diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index dd0c3c8135..04e784e2ba 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -23,11 +23,13 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false property bool isDesktop: false - property bool removingPage: false - property bool loadingPage: false + property string initialPage: "" + property bool startingUp: true property alias webView: webview property alias profile: webview.profile property bool remove: false + property var urlList: [] + property var forwardList: [] property int currentPage: -1 // used as a model for repeater @@ -79,9 +81,12 @@ Item { id: displayUrl color: hifi.colors.baseGray font.pixelSize: 12 + verticalAlignment: Text.AlignLeft anchors { top: nav.bottom horizontalCenter: parent.horizontalCenter; + left: parent.left + leftMargin: 20 } } @@ -106,19 +111,19 @@ Item { } function goBack() { - if (webview.canGoBack && !isUrlLoaded(webview.url)) { - if (currentPage > 0) { - removingPage = true; - pagesModel.remove(currentPage); - } + if (webview.canGoBack) { + forwardList.push(webview.url); webview.goBack(); - } else if (currentPage > 0) { - removingPage = true; - pagesModel.remove(currentPage); + } else if (web.urlList.length > 0) { + var url = web.urlList.pop(); + loadUrl(url); + } else if (web.forwardList.length > 0) { + var url = web.forwardList.pop(); + loadUrl(url); + web.forwardList = []; } } - function closeWebEngine() { if (remove) { web.destroy(); @@ -151,32 +156,42 @@ Item { view.setEnabled(true); } + function loadUrl(url) { + webview.url = url + web.url = webview.url; + web.address = webview.url; + } + + function onInitialPage(url) { + return (url === webview.url); + } + + function urlAppend(url) { - if (removingPage) { - removingPage = false; - return; - } var lurl = decodeURIComponent(url) if (lurl[lurl.length - 1] !== "/") { lurl = lurl + "/" } - if (currentPage === -1 || (pagesModel.get(currentPage).webUrl !== lurl && !timer.running)) { - timer.start(); - pagesModel.append({webUrl: lurl}); - } + web.urlList.push(url); + setBackButtonStatus(); } - onCurrentPageChanged: { - if (currentPage >= 0 && currentPage < pagesModel.count) { - timer.start(); - webview.url = pagesModel.get(currentPage).webUrl; - web.url = webview.url; - web.address = webview.url; + function setBackButtonStatus() { + if (web.urlList.length > 0 || webview.canGoBack) { + back.enabledColor = hifi.colors.darkGray; + back.enabled = true; + } else { + back.enabledColor = hifi.colors.baseGray; + back.enabled = false; } } onUrlChanged: { - gotoPage(url) + loadUrl(url); + if (startingUp) { + web.initialPage = webview.url; + startingUp = false; + } } QtObject { @@ -184,18 +199,7 @@ Item { WebChannel.id: "eventBridgeWrapper" property var eventBridge; } - - Timer { - id: timer - interval: 200 - running: false - repeat: false - onTriggered: timer.stop(); - } - - - - + WebEngineView { id: webview objectName: "webEngineView" @@ -235,6 +239,7 @@ Item { worldId: WebEngineScript.MainWorld } + property string urlTag: "noDownload=false"; userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] property string newUrl: "" @@ -261,9 +266,7 @@ Item { keyboard.resetShiftMode(false); // Required to support clicking on "hifi://" links if (WebEngineView.LoadStartedStatus == loadRequest.status) { - urlAppend(loadRequest.url.toString()); - loadingPage = true; - var url = loadRequest.url.toString(); + var url = loadRequest.url.toString(); if (urlHandler.canHandleUrl(url)) { if (urlHandler.handleUrl(url)) { root.stop(); @@ -274,9 +277,19 @@ Item { if (WebEngineView.LoadFailedStatus == loadRequest.status) { console.log(" Tablet WebEngineView failed to laod url: " + loadRequest.url.toString()); } - } + if (WebEngineView.LoadSucceededStatus == loadRequest.status) { + web.address = webview.url; + if (startingUp) { + web.initialPage = webview.url; + startingUp = false; + } + } + } + onNewViewRequested: { + var currentUrl = webview.url; + urlAppend(currentUrl); request.openIn(webview); } } diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 0b34a8f9ac..9617b41150 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -31,7 +31,7 @@ Item { property bool drillDownToPlace: false; property bool showPlace: isConcurrency; - property string messageColor: hifi.colors.blueAccent; + property string messageColor: isAnnouncement ? "white" : hifi.colors.blueAccent; property string timePhrase: pastTime(timestamp); property int onlineUsers: 0; property bool isConcurrency: action === 'concurrency'; @@ -71,6 +71,10 @@ Item { property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4); + function pluralize(count, singular, optionalPlural) { + return (count === 1) ? singular : (optionalPlural || (singular + "s")); + } + DropShadow { visible: isStacked; anchors.fill: shadow1; @@ -115,7 +119,7 @@ Item { id: lobby; visible: !hasGif || (animation.status !== Image.Ready); width: parent.width - (isConcurrency ? 0 : (2 * smallMargin)); - height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin); + height: parent.height -(isAnnouncement ? smallMargin : messageHeight) - (isConcurrency ? 0 : smallMargin); source: thumbnail || defaultThumbnail; fillMode: Image.PreserveAspectCrop; anchors { @@ -160,7 +164,24 @@ Item { margins: textPadding; } } + Rectangle { + id: lozenge; + visible: isAnnouncement; + color: hifi.colors.redHighlight; + anchors.fill: infoRow; + radius: lozenge.height / 2.0; + border.width: lozengeHot.containsMouse ? 4 : 0; + border.color: "white"; + } Row { + id: infoRow; + Image { + id: icon; + source: isAnnouncement ? "../../images/Announce-Blast.svg" : "../../images/snap-icon.svg"; + width: 40; + height: 40; + visible: ((action === 'snapshot') || isAnnouncement) && (messageHeight >= 40); + } FiraSansRegular { id: users; visible: isConcurrency || isAnnouncement; @@ -169,34 +190,42 @@ Item { color: messageColor; anchors.verticalCenter: message.verticalCenter; } - Image { - id: icon; - source: "../../images/snap-icon.svg" - width: 40; - height: 40; - visible: (action === 'snapshot') && (messageHeight >= 40); - } RalewayRegular { id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (isAnnouncement ? "connections" : (drillDownToPlace ? "snapshots" : ("by " + userName))); + visible: !isAnnouncement; + text: isConcurrency ? pluralize(onlineUsers, "person", "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); size: textSizeSmall; color: messageColor; elide: Text.ElideRight; // requires a width to be specified` width: root.width - textPadding - - (users.visible ? users.width + parent.spacing : 0) - (icon.visible ? icon.width + parent.spacing : 0) + - (users.visible ? users.width + parent.spacing : 0) - (actionIcon.width + (2 * smallMargin)); anchors { bottom: parent.bottom; bottomMargin: parent.spacing; } } + Column { + visible: isAnnouncement; + RalewayRegular { + text: pluralize(onlineUsers, "connection") + " "; // hack padding + size: textSizeSmall; + color: messageColor; + } + RalewayRegular { + text: pluralize(onlineUsers, "is here now", "are here now"); + size: textSizeSmall * 0.7; + color: messageColor; + } + } spacing: textPadding; height: messageHeight; anchors { bottom: parent.bottom; left: parent.left; leftMargin: textPadding; + bottomMargin: isAnnouncement ? textPadding : 0; } } // These two can be supplied to provide hover behavior. @@ -214,6 +243,7 @@ Item { } StateImage { id: actionIcon; + visible: !isAnnouncement; imageURL: "../../images/info-icon-2-state.svg"; size: 30; buttonState: messageArea.containsMouse ? 1 : 0; @@ -223,13 +253,25 @@ Item { margins: smallMargin; } } + function go() { + goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); + } MouseArea { id: messageArea; + visible: !isAnnouncement; width: parent.width; height: messageHeight; anchors.top: lobby.bottom; acceptedButtons: Qt.LeftButton; - onClicked: goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); + onClicked: go(); + hoverEnabled: true; + } + MouseArea { + id: lozengeHot; + visible: lozenge.visible; + anchors.fill: lozenge; + acceptedButtons: Qt.LeftButton; + onClicked: go(); hoverEnabled: true; } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index b7c0d24b24..7159b078ee 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -280,7 +280,6 @@ StackView { cardHeight: 163 + (2 * 4); metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'HAPPENING NOW'; - //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. actions: 'announcement'; filter: addressLine.text; goFunction: goCard; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a8dbb2f4fc..1e3df955ba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5423,9 +5423,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setEntityTree(getEntities()->getTree()); - // AvatarManager has some custom types - AvatarManager::registerMetaTypes(scriptEngine); - // give the script engine to the RecordingScriptingInterface for its callbacks DependencyManager::get()->setScriptEngine(scriptEngine); diff --git a/interface/src/FancyCamera.h b/interface/src/FancyCamera.h index cd231cd929..66f7a07dbd 100644 --- a/interface/src/FancyCamera.h +++ b/interface/src/FancyCamera.h @@ -13,9 +13,8 @@ #include "Camera.h" -#include +#include -// TODO: come up with a better name than "FancyCamera" class FancyCamera : public Camera { Q_OBJECT diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index fcd539ca7d..9688694287 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -495,12 +495,15 @@ Menu::Menu() { qApp, SLOT(setActiveEyeTracker())); #endif - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, - avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false); + connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false); + connect(action, &QAction::triggered, [this]{ Avatar::setShowCollisionShapes(isOptionChecked(MenuOption::ShowBoundingCollisionShapes)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false); + connect(action, &QAction::triggered, [this]{ Avatar::setShowMyLookAtVectors(isOptionChecked(MenuOption::ShowMyLookAtVectors)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false); + connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); }); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderMyLookAtVectors, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderOtherLookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 479a78e7c2..250d2241ac 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -145,9 +145,6 @@ namespace MenuOption { const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadContent = "Reload Content (Clears all caches)"; - const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; - const QString RenderMyLookAtVectors = "Show My Eye Vectors"; - const QString RenderOtherLookAtVectors = "Show Other Eye Vectors"; const QString RenderMaxTextureMemory = "Maximum Texture Memory"; const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; const QString RenderMaxTexture4MB = "4 MB"; @@ -173,8 +170,11 @@ namespace MenuOption { const QString SendWrongDSConnectVersion = "Send wrong DS connect version"; const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; - const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; + const QString ShowBoundingCollisionShapes = "Show Bounding Collision Shapes"; + const QString ShowDSConnectTable = "Show Domain Connection Timing"; + const QString ShowMyLookAtVectors = "Show My Eye Vectors"; + const QString ShowOtherLookAtVectors = "Show Other Eye Vectors"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; const QString SimulateEyeTracking = "Simulate"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ce8ec44f6c..3bd4c663d2 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -28,10 +28,8 @@ #include #include -#include "AvatarManager.h" #include "AvatarMotionState.h" #include "Camera.h" -#include "Menu.h" #include "InterfaceLogging.h" #include "SceneScriptingInterface.h" #include "SoftAttachmentModel.h" @@ -59,7 +57,6 @@ namespace render { auto avatarPtr = static_pointer_cast(avatar); if (avatarPtr->isInitialized() && args) { PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload"); - // TODO AVATARS_RENDERER: remove need for qApp avatarPtr->render(args); } } @@ -74,6 +71,31 @@ namespace render { } } +static bool showReceiveStats = false; +void Avatar::setShowReceiveStats(bool receiveStats) { + showReceiveStats = receiveStats; +} + +static bool showMyLookAtVectors = false; +void Avatar::setShowMyLookAtVectors(bool showMine) { + showMyLookAtVectors = showMine; +} + +static bool showOtherLookAtVectors = false; +void Avatar::setShowOtherLookAtVectors(bool showOthers) { + showOtherLookAtVectors = showOthers; +} + +static bool showCollisionShapes = false; +void Avatar::setShowCollisionShapes(bool render) { + showCollisionShapes = render; +} + +static bool showNamesAboveHeads = false; +void Avatar::setShowNamesAboveHeads(bool show) { + showNamesAboveHeads = show; +} + Avatar::Avatar(QThread* thread, RigPointer rig) : AvatarData(), _skeletonOffset(0.0f), @@ -126,11 +148,6 @@ Avatar::~Avatar() { }); } - if (_motionState) { - delete _motionState; - _motionState = nullptr; - } - auto geometryCache = DependencyManager::get(); if (geometryCache) { geometryCache->releaseID(_nameRectGeometryID); @@ -350,7 +367,7 @@ void Avatar::simulate(float deltaTime, bool inView) { _smoothPositionTimer += deltaTime; if (_smoothPositionTimer < _smoothPositionTime) { AvatarData::setPosition( - lerp(_smoothPositionInitial, + lerp(_smoothPositionInitial, _smoothPositionTarget, easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f))) ); @@ -363,7 +380,7 @@ void Avatar::simulate(float deltaTime, bool inView) { _smoothOrientationTimer += deltaTime; if (_smoothOrientationTimer < _smoothOrientationTime) { AvatarData::setOrientation( - slerp(_smoothOrientationInitial, + slerp(_smoothOrientationInitial, _smoothOrientationTarget, easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f))) ); @@ -535,14 +552,7 @@ void Avatar::updateRenderItem(render::Transaction& transaction) { void Avatar::postUpdate(float deltaTime) { - bool renderLookAtVectors; - if (isMyAvatar()) { - renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors); - } else { - renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors); - } - - if (renderLookAtVectors) { + if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); @@ -583,7 +593,6 @@ void Avatar::render(RenderArgs* renderArgs) { bool havePosition, haveRotation; if (_handState & LEFT_HAND_POINTING_FLAG) { - if (_handState & IS_FINGER_POINTING_FLAG) { int leftIndexTip = getJointIndex("LeftHandIndex4"); int leftIndexTipJoint = getJointIndex("LeftHandIndex3"); @@ -636,44 +645,25 @@ void Avatar::render(RenderArgs* renderArgs) { return; } - glm::vec3 toTarget = frustum.getPosition() - getPosition(); - float distanceToTarget = glm::length(toTarget); + fixupModelsInScene(renderArgs->_scene); - { - fixupModelsInScene(renderArgs->_scene); - - if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) { - // add local lights - const float BASE_LIGHT_DISTANCE = 2.0f; - const float LIGHT_FALLOFF_RADIUS = 0.01f; - const float LIGHT_EXPONENT = 1.0f; - const float LIGHT_CUTOFF = glm::radians(80.0f); - float distance = BASE_LIGHT_DISTANCE * getUniformScale(); - glm::vec3 position = _skeletonModel->getTranslation(); - glm::quat orientation = getOrientation(); - foreach (const AvatarManager::LocalLight& light, DependencyManager::get()->getLocalLights()) { - glm::vec3 direction = orientation * light.direction; - DependencyManager::get()->addSpotLight(position - direction * distance, - distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF); - } - } - - bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); - if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) { - PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes"); - const float BOUNDING_SHAPE_ALPHA = 0.7f; - _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA); - } + if (showCollisionShapes && shouldRenderHead(renderArgs) && _skeletonModel->isRenderable()) { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes"); + const float BOUNDING_SHAPE_ALPHA = 0.7f; + _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA); } - const float DISPLAYNAME_DISTANCE = 20.0f; - setShowDisplayName(distanceToTarget < DISPLAYNAME_DISTANCE); - - if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) { - auto& frustum = renderArgs->getViewFrustum(); - auto textPosition = getDisplayNamePosition(); - if (frustum.pointIntersectsFrustum(textPosition)) { - renderDisplayName(batch, frustum, textPosition); + if (showReceiveStats || showNamesAboveHeads) { + glm::vec3 toTarget = frustum.getPosition() - getPosition(); + float distanceToTarget = glm::length(toTarget); + const float DISPLAYNAME_DISTANCE = 20.0f; + updateDisplayNameAlpha(distanceToTarget < DISPLAYNAME_DISTANCE); + if (!isMyAvatar() || renderArgs->_cameraMode != (int8_t)CAMERA_MODE_FIRST_PERSON) { + auto& frustum = renderArgs->getViewFrustum(); + auto textPosition = getDisplayNamePosition(); + if (frustum.pointIntersectsFrustum(textPosition)) { + renderDisplayName(batch, frustum, textPosition); + } } } } @@ -829,7 +819,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const g void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const { PROFILE_RANGE_BATCH(batch, __FUNCTION__); - bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats() && !isMyAvatar(); + bool shouldShowReceiveStats = showReceiveStats && !isMyAvatar(); // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return static const float CLIP_DISTANCE = 0.2f; @@ -1200,8 +1190,8 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) { const float MOVE_DISTANCE_THRESHOLD = 0.001f; _moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD; - if (_moving && _motionState) { - _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); + if (_moving) { + addPhysicsFlags(Simulation::DIRTY_POSITION); } if (_moving || _hasNewJointData) { locationChanged(); @@ -1284,8 +1274,8 @@ float Avatar::getPelvisFloatingHeight() const { return -_skeletonModel->getBindExtents().minimum.y; } -void Avatar::setShowDisplayName(bool showDisplayName) { - if (!Menu::getInstance()->isOptionChecked(MenuOption::NamesAboveHeads)) { +void Avatar::updateDisplayNameAlpha(bool showDisplayName) { + if (!(showNamesAboveHeads || showReceiveStats)) { _displayNameAlpha = 0.0f; return; } @@ -1323,14 +1313,18 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { radius = halfExtents.x; } -void Avatar::setMotionState(AvatarMotionState* motionState) { - _motionState = motionState; -} - // virtual void Avatar::rebuildCollisionShape() { - if (_motionState) { - _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE); + addPhysicsFlags(Simulation::DIRTY_SHAPE); +} + +void Avatar::setPhysicsCallback(AvatarPhysicsCallback cb) { + _physicsCallback = cb; +} + +void Avatar::addPhysicsFlags(uint32_t flags) { + if (_physicsCallback) { + _physicsCallback(flags); } } @@ -1493,16 +1487,16 @@ QList Avatar::getSkeleton() { void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer& scene) { if (scene) { - render::Transaction transaction; auto nodelist = DependencyManager::get(); if (DependencyManager::get()->shouldRenderAvatars() && !nodelist->isIgnoringNode(getSessionUUID()) && !nodelist->isRadiusIgnoringNode(getSessionUUID())) { + render::Transaction transaction; addToScene(myHandle, scene, transaction); + scene->enqueueTransaction(transaction); } - scene->enqueueTransaction(transaction); } else { - qCWarning(interfaceapp) << "AvatarManager::addAvatar() : Unexpected null scene, possibly during application shutdown"; + qCWarning(interfaceapp) << "Avatar::addAvatar() : Unexpected null scene, possibly during application shutdown"; } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 14d1da530a..f86bf35bd9 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -11,6 +11,7 @@ #ifndef hifi_Avatar_h #define hifi_Avatar_h +#include #include #include @@ -48,9 +49,10 @@ enum ScreenTintLayer { NUM_SCREEN_TINT_LAYERS }; -class AvatarMotionState; class Texture; +using AvatarPhysicsCallback = std::function; + class Avatar : public AvatarData { Q_OBJECT @@ -66,6 +68,12 @@ class Avatar : public AvatarData { Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset) public: + static void setShowReceiveStats(bool receiveStats); + static void setShowMyLookAtVectors(bool showMine); + static void setShowOtherLookAtVectors(bool showOthers); + static void setShowCollisionShapes(bool render); + static void setShowNamesAboveHeads(bool show); + explicit Avatar(QThread* thread, RigPointer rig = nullptr); ~Avatar(); @@ -148,7 +156,7 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; - void setShowDisplayName(bool showDisplayName); + void updateDisplayNameAlpha(bool showDisplayName); virtual void setSessionDisplayName(const QString& sessionDisplayName) override { }; // no-op virtual int parseDataFromBuffer(const QByteArray& buffer) override; @@ -185,8 +193,6 @@ public: virtual void computeShapeInfo(ShapeInfo& shapeInfo); void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); - AvatarMotionState* getMotionState() { return _motionState; } - using SpatiallyNestable::setPosition; virtual void setPosition(const glm::vec3& position) override; using SpatiallyNestable::setOrientation; @@ -238,6 +244,17 @@ public: return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); } + float getBoundingRadius() const; + + void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene); + void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene); + bool isInScene() const { return render::Item::isValidID(_renderItemID); } + bool isMoving() const { return _moving; } + + //void setMotionState(AvatarMotionState* motionState); + void setPhysicsCallback(AvatarPhysicsCallback cb); + void addPhysicsFlags(uint32_t flags); + bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; } public slots: @@ -251,7 +268,6 @@ public slots: void setModelURLFinished(bool success); protected: - friend class AvatarManager; const float SMOOTH_TIME_POSITION = 0.125f; const float SMOOTH_TIME_ORIENTATION = 0.075f; @@ -260,8 +276,6 @@ protected: QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! - void setMotionState(AvatarMotionState* motionState); - SkeletonModelPointer _skeletonModel; glm::vec3 _skeletonOffset; std::vector> _attachmentModels; @@ -315,10 +329,6 @@ protected: ThreadSafeValueCache _rightPalmPositionCache { glm::vec3() }; ThreadSafeValueCache _rightPalmRotationCache { glm::quat() }; - void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene); - void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene); - bool isInScene() const { return render::Item::isValidID(_renderItemID); } - // Some rate tracking support RateCounter<> _simulationRate; RateCounter<> _simulationInViewRate; @@ -354,13 +364,15 @@ private: bool _isLookAtTarget { false }; bool _isAnimatingScale { false }; - float getBoundingRadius() const; - static int _jointConesID; int _voiceSphereID; - AvatarMotionState* _motionState = nullptr; + AvatarPhysicsCallback _physicsCallback { nullptr }; + + float _displayNameTargetAlpha { 1.0f }; + float _displayNameAlpha { 1.0f }; + }; #endif // hifi_Avatar_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c4bcb67a16..04ab1531ba 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -50,23 +50,6 @@ static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. const QUuid MY_AVATAR_KEY; // NULL key -static QScriptValue localLightToScriptValue(QScriptEngine* engine, const AvatarManager::LocalLight& light) { - QScriptValue object = engine->newObject(); - object.setProperty("direction", vec3toScriptValue(engine, light.direction)); - object.setProperty("color", vec3toScriptValue(engine, light.color)); - return object; -} - -static void localLightFromScriptValue(const QScriptValue& value, AvatarManager::LocalLight& light) { - vec3FromScriptValue(value.property("direction"), light.direction); - vec3FromScriptValue(value.property("color"), light.color); -} - -void AvatarManager::registerMetaTypes(QScriptEngine* engine) { - qScriptRegisterMetaType(engine, localLightToScriptValue, localLightFromScriptValue); - qScriptRegisterSequenceMetaType >(engine); -} - AvatarManager::AvatarManager(QObject* parent) : _avatarsToFade(), _myAvatar(std::make_shared(qApp->thread(), std::make_shared())) @@ -91,6 +74,7 @@ AvatarManager::AvatarManager(QObject* parent) : } AvatarManager::~AvatarManager() { + assert(_motionStates.empty()); } void AvatarManager::init() { @@ -145,10 +129,9 @@ float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = std::static_pointer_cast(getAvatarBySessionID(sessionID)); - return avatar ? avatar->getSimulationRate(rateName) : 0.0f; + return avatar ? avatar->getSimulationRate(rateName) : 0.0f; } - void AvatarManager::updateOtherAvatars(float deltaTime) { // lock the hash for read to check the size QReadLocker lock(&_hashLock); @@ -200,16 +183,15 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } - if (!avatar->getMotionState()) { + if (!avatar->isInPhysicsSimulation()) { ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { - // don't add to the simulation now, instead put it on a list to be added later - AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape); - avatar->setMotionState(motionState); + AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); + avatar->setPhysicsCallback([=] (uint32_t flags) { motionState->addDirtyFlags(flags); }); + _motionStates.insert(avatar.get(), motionState); _motionStatesToAddToPhysics.insert(motionState); - _motionStatesThatMightUpdate.insert(motionState); } } avatar->animateScaleChanges(deltaTime); @@ -294,30 +276,24 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { const float MIN_FADE_SCALE = MIN_AVATAR_SCALE; QReadLocker locker(&_hashLock); - QVector::iterator itr = _avatarsToFade.begin(); - while (itr != _avatarsToFade.end()) { - auto avatar = std::static_pointer_cast(*itr); + QVector::iterator avatarItr = _avatarsToFade.begin(); + while (avatarItr != _avatarsToFade.end()) { + auto avatar = std::static_pointer_cast(*avatarItr); avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE); avatar->animateScaleChanges(deltaTime); if (avatar->getTargetScale() <= MIN_FADE_SCALE) { - // fading to zero is such a rare event we push unique transaction for each one + // fading to zero is such a rare event we push a unique transaction for each if (avatar->isInScene()) { const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; - avatar->removeFromScene(*itr, scene, transaction); + avatar->removeFromScene(*avatarItr, scene, transaction); scene->enqueueTransaction(transaction); } - - // only remove from _avatarsToFade if we're sure its motionState has been removed from PhysicsEngine - if (_motionStatesToRemoveFromPhysics.empty()) { - itr = _avatarsToFade.erase(itr); - } else { - ++itr; - } + avatarItr = _avatarsToFade.erase(avatarItr); } else { const bool inView = true; // HACK avatar->simulate(deltaTime, inView); - ++itr; + ++avatarItr; } } } @@ -329,15 +305,15 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() { void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason); - // removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar - // class in this context so we can call methods that don't exist at the base class. + // remove from physics auto avatar = std::static_pointer_cast(removedAvatar); - - AvatarMotionState* motionState = avatar->getMotionState(); - if (motionState) { - _motionStatesThatMightUpdate.remove(motionState); + avatar->setPhysicsCallback(nullptr); + AvatarMotionStateMap::iterator itr = _motionStates.find(avatar.get()); + if (itr != _motionStates.end()) { + AvatarMotionState* motionState = *itr; _motionStatesToAddToPhysics.remove(motionState); _motionStatesToRemoveFromPhysics.push_back(motionState); + _motionStates.erase(itr); } if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { @@ -373,11 +349,15 @@ void AvatarManager::clearOtherAvatars() { ++avatarIterator; } } + assert(scene); scene->enqueueTransaction(transaction); _myAvatar->clearLookAtTargetAvatar(); } void AvatarManager::deleteAllAvatars() { + assert(_motionStates.empty()); // should have called clearOtherAvatars() before getting here + deleteMotionStates(); + QReadLocker locker(&_hashLock); AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { @@ -387,27 +367,18 @@ void AvatarManager::deleteAllAvatars() { } } -void AvatarManager::setLocalLights(const QVector& localLights) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setLocalLights", Q_ARG(const QVector&, localLights)); - return; +void AvatarManager::deleteMotionStates() { + // delete motionstates that were removed from physics last frame + for (auto state : _motionStatesToDelete) { + delete state; } - _localLights = localLights; -} - -QVector AvatarManager::getLocalLights() const { - if (QThread::currentThread() != thread()) { - QVector result; - QMetaObject::invokeMethod(const_cast(this), "getLocalLights", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVector, result)); - return result; - } - return _localLights; + _motionStatesToDelete.clear(); } void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { - result.clear(); - result.swap(_motionStatesToRemoveFromPhysics); + deleteMotionStates(); + result = _motionStatesToRemoveFromPhysics; + _motionStatesToDelete.swap(_motionStatesToRemoveFromPhysics); } void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) { @@ -420,10 +391,12 @@ void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) { void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); - for (auto state : _motionStatesThatMightUpdate) { - if (state->_dirtyFlags > 0) { - result.push_back(state); + AvatarMotionStateMap::iterator motionStateItr = _motionStates.begin(); + while (motionStateItr != _motionStates.end()) { + if ((*motionStateItr)->getIncomingDirtyFlags() != 0) { + result.push_back(*motionStateItr); } + ++motionStateItr; } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 45f1a597eb..c67088a4be 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -62,18 +62,6 @@ public: void clearOtherAvatars(); void deleteAllAvatars(); - bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } - - class LocalLight { - public: - glm::vec3 color; - glm::vec3 direction; - }; - - Q_INVOKABLE void setLocalLights(const QVector& localLights); - Q_INVOKABLE QVector getLocalLights() const; - - void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates); void getObjectsToChange(VectorOfMotionStates& motionStates); @@ -95,7 +83,6 @@ public: float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } public slots: - void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); private: @@ -105,21 +92,20 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; + void deleteMotionStates(); void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; QVector _avatarsToFade; - QSet _motionStatesThatMightUpdate; + using AvatarMotionStateMap = QMap; + AvatarMotionStateMap _motionStates; VectorOfMotionStates _motionStatesToRemoveFromPhysics; + VectorOfMotionStates _motionStatesToDelete; SetOfMotionStates _motionStatesToAddToPhysics; std::shared_ptr _myAvatar; quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. - QVector _localLights; - - bool _shouldShowReceiveStats = false; - std::list> _collisionInjectors; RateCounter<> _myAvatarSendRate; @@ -129,7 +115,4 @@ private: bool _shouldRender { true }; }; -Q_DECLARE_METATYPE(AvatarManager::LocalLight) -Q_DECLARE_METATYPE(QVector) - #endif // hifi_AvatarManager_h diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 335245670b..ffa99e3990 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -17,7 +17,7 @@ #include "AvatarMotionState.h" #include "BulletUtil.h" -AvatarMotionState::AvatarMotionState(Avatar* avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { +AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; if (_shape) { @@ -49,7 +49,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { // virtual and protected const btCollisionShape* AvatarMotionState::computeNewShape() { ShapeInfo shapeInfo; - _avatar->computeShapeInfo(shapeInfo); + std::static_pointer_cast(_avatar)->computeShapeInfo(shapeInfo); return getShapeManager()->getShape(shapeInfo); } @@ -130,7 +130,7 @@ glm::vec3 AvatarMotionState::getObjectAngularVelocity() const { // virtual glm::vec3 AvatarMotionState::getObjectGravity() const { - return _avatar->getAcceleration(); + return std::static_pointer_cast(_avatar)->getAcceleration(); } // virtual diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 98b2b69373..a8dd7327ca 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -20,7 +20,7 @@ class Avatar; class AvatarMotionState : public ObjectMotionState { public: - AvatarMotionState(Avatar* avatar, const btCollisionShape* shape); + AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape); virtual PhysicsMotionType getMotionType() const override { return _motionType; } @@ -74,11 +74,7 @@ protected: virtual bool isReadyToComputeShape() const override { return true; } virtual const btCollisionShape* computeNewShape() override; - // The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState - // instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor. - // In other words, it is impossible for the Avatar to be deleted out from under its MotionState. - // In conclusion: weak pointer shennanigans would be pure overhead. - Avatar* _avatar; // do NOT use smartpointer here, no need for weakpointer + AvatarSharedPointer _avatar; uint32_t _dirtyFlags; }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index bf4be7fd17..e2e22fe366 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -111,7 +111,7 @@ void setupPreferences() { static const QString SNAPSHOTS { "Snapshots" }; { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; - auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; + auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); emit DependencyManager::get()->snapshotLocationSet(value); }; auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter); preferences->addPreference(preference); } diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 93ffbbc7bb..1246e1d004 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -44,6 +44,9 @@ public: static Setting::Handle snapshotsLocation; static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl("")); +signals: + void snapshotLocationSet(const QString& value); + public slots: Q_INVOKABLE QString getSnapshotsLocation(); Q_INVOKABLE void setSnapshotsLocation(const QString& location); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6c265ef1b6..cb819c6b20 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -63,8 +63,6 @@ AvatarData::AvatarData() : _keyState(NO_KEY_DOWN), _forceFaceTrackerConnected(false), _headData(NULL), - _displayNameTargetAlpha(1.0f), - _displayNameAlpha(1.0f), _errorLogExpiry(0), _owningAvatarMixer(), _targetVelocity(0.0f) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b2cc912007..b4c79470de 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -692,9 +692,6 @@ protected: QString _sessionDisplayName { }; QUrl cannonicalSkeletonModelURL(const QUrl& empty) const; - float _displayNameTargetAlpha; - float _displayNameAlpha; - QHash _jointIndices; ///< 1-based, since zero is returned for missing keys QStringList _jointNames; ///< in order of depth-first traversal diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp index b86391bbba..fc0891799e 100644 --- a/libraries/controllers/src/controllers/Pose.cpp +++ b/libraries/controllers/src/controllers/Pose.cpp @@ -69,5 +69,23 @@ namespace controller { pose.valid = valid; return pose; } + + Pose Pose::postTransform(const glm::mat4& mat) const { + glm::mat4 original = ::createMatFromQuatAndPos(rotation, translation); + glm::mat4 result = original * mat; + auto translationOut = ::extractTranslation(result); + auto rotationOut = ::glmExtractRotation(result); + auto velocityOut = velocity + glm::cross(angularVelocity, translationOut - translation); // warning: this may be completely wrong + auto angularVelocityOut = angularVelocity; + + Pose pose(translationOut, + rotationOut, + velocityOut, + angularVelocityOut); + + pose.valid = valid; + return pose; + } + } diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h index 47ba59279a..a6d1360f9f 100644 --- a/libraries/controllers/src/controllers/Pose.h +++ b/libraries/controllers/src/controllers/Pose.h @@ -41,6 +41,7 @@ namespace controller { vec3 getAngularVelocity() const { return angularVelocity; } Pose transform(const glm::mat4& mat) const; + Pose postTransform(const glm::mat4& mat) const; static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event); static void fromScriptValue(const QScriptValue& object, Pose& event); diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 71b052bfe4..570081d1f1 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -20,6 +20,8 @@ #include #include +#include + #include "StandardController.h" #include "StateController.h" #include "InputRecorder.h" @@ -563,7 +565,18 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { if (source->isPose()) { Pose value = getPose(source, route->peek); static const Pose IDENTITY_POSE { vec3(), quat() }; + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Value was t:" << value.translation << "r:" << value.rotation; + } + // Apply each of the filters. + for (const auto& filter : route->filters) { + value = filter->apply(value); + } + + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Filtered value was t:" << value.translation << "r:" << value.rotation; + if (!value.valid) { qCDebug(controllers) << "Applying invalid pose"; } else if (value == IDENTITY_POSE) { diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index 09188318eb..2cb35d85ce 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -24,6 +24,10 @@ #include "filters/InvertFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" +#include "filters/TranslateFilter.h" +#include "filters/TransformFilter.h" +#include "filters/PostTransformFilter.h" +#include "filters/RotateFilter.h" using namespace controller; @@ -37,6 +41,10 @@ REGISTER_FILTER_CLASS_INSTANCE(HysteresisFilter, "hysteresis") REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert") REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale") REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse") +REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate") +REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform") +REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform") +REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate") const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); @@ -76,7 +84,6 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri return true; } } else if (parameters.isObject()) { - static const QString JSON_MIN = QStringLiteral("interval"); auto objectParameters = parameters.toObject(); if (objectParameters.contains(name)) { output = objectParameters[name].toDouble(); @@ -86,6 +93,92 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri return false; } +bool Filter::parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output) { + if (parameters.isDouble()) { + output = glm::vec3(parameters.toDouble()); + return true; + } else if (parameters.isArray()) { + auto arrayParameters = parameters.toArray(); + if (arrayParameters.size() == 3) { + output = glm::vec3(arrayParameters[0].toDouble(), + arrayParameters[1].toDouble(), + arrayParameters[2].toDouble()); + return true; + } + } else if (parameters.isObject()) { + auto objectParameters = parameters.toObject(); + if (objectParameters.contains("x") && objectParameters.contains("y") && objectParameters.contains("z")) { + output = glm::vec3(objectParameters["x"].toDouble(), + objectParameters["y"].toDouble(), + objectParameters["z"].toDouble()); + return true; + } + } + return false; +} + +bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) { + if (parameters.isObject()) { + auto objectParameters = parameters.toObject(); + + + if (objectParameters.contains("r0c0") && + objectParameters.contains("r1c0") && + objectParameters.contains("r2c0") && + objectParameters.contains("r3c0") && + objectParameters.contains("r0c1") && + objectParameters.contains("r1c1") && + objectParameters.contains("r2c1") && + objectParameters.contains("r3c1") && + objectParameters.contains("r0c2") && + objectParameters.contains("r1c2") && + objectParameters.contains("r2c2") && + objectParameters.contains("r3c2") && + objectParameters.contains("r0c3") && + objectParameters.contains("r1c3") && + objectParameters.contains("r2c3") && + objectParameters.contains("r3c3")) { + + output[0][0] = objectParameters["r0c0"].toDouble(); + output[0][1] = objectParameters["r1c0"].toDouble(); + output[0][2] = objectParameters["r2c0"].toDouble(); + output[0][3] = objectParameters["r3c0"].toDouble(); + output[1][0] = objectParameters["r0c1"].toDouble(); + output[1][1] = objectParameters["r1c1"].toDouble(); + output[1][2] = objectParameters["r2c1"].toDouble(); + output[1][3] = objectParameters["r3c1"].toDouble(); + output[2][0] = objectParameters["r0c2"].toDouble(); + output[2][1] = objectParameters["r1c2"].toDouble(); + output[2][2] = objectParameters["r2c2"].toDouble(); + output[2][3] = objectParameters["r3c2"].toDouble(); + output[3][0] = objectParameters["r0c3"].toDouble(); + output[3][1] = objectParameters["r1c3"].toDouble(); + output[3][2] = objectParameters["r2c3"].toDouble(); + output[3][3] = objectParameters["r3c3"].toDouble(); + + return true; + } + } + return false; +} + +bool Filter::parseQuatParameter(const QJsonValue& parameters, glm::quat& output) { + if (parameters.isObject()) { + auto objectParameters = parameters.toObject(); + if (objectParameters.contains("w") && + objectParameters.contains("x") && + objectParameters.contains("y") && + objectParameters.contains("z")) { + + output = glm::quat(objectParameters["w"].toDouble(), + objectParameters["x"].toDouble(), + objectParameters["y"].toDouble(), + objectParameters["z"].toDouble()); + return true; + } + } + return false; +} #if 0 diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h index 77585c8ebb..cde8f991b7 100644 --- a/libraries/controllers/src/controllers/impl/Filter.h +++ b/libraries/controllers/src/controllers/impl/Filter.h @@ -21,6 +21,8 @@ #include +#include "../Pose.h" + class QJsonValue; namespace controller { @@ -34,6 +36,8 @@ namespace controller { using Factory = hifi::SimpleFactory; virtual float apply(float value) const = 0; + virtual Pose apply(Pose value) const = 0; + // Factory features virtual bool parseParameters(const QJsonValue& parameters) { return true; } @@ -42,6 +46,9 @@ namespace controller { static Factory& getFactory() { return _factory; } static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output); + static bool parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output); + static bool parseQuatParameter(const QJsonValue& parameters, glm::quat& output); + static bool parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output); protected: static Factory _factory; }; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 7dedfda3cb..f3c447238a 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -26,6 +26,10 @@ #include "filters/InvertFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" +#include "filters/TranslateFilter.h" +#include "filters/TransformFilter.h" +#include "filters/PostTransformFilter.h" +#include "filters/RotateFilter.h" #include "conditionals/AndConditional.h" using namespace controller; @@ -103,6 +107,26 @@ QObject* RouteBuilderProxy::deadZone(float min) { return this; } +QObject* RouteBuilderProxy::translate(glm::vec3 translate) { + addFilter(std::make_shared(translate)); + return this; +} + +QObject* RouteBuilderProxy::transform(glm::mat4 transform) { + addFilter(std::make_shared(transform)); + return this; +} + +QObject* RouteBuilderProxy::postTransform(glm::mat4 transform) { + addFilter(std::make_shared(transform)); + return this; +} + +QObject* RouteBuilderProxy::rotate(glm::quat rotation) { + addFilter(std::make_shared(rotation)); + return this; +} + QObject* RouteBuilderProxy::constrainToInteger() { addFilter(std::make_shared()); return this; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 1c0ed6931d..de9c23d2cd 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -48,6 +48,10 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* deadZone(float min); Q_INVOKABLE QObject* constrainToInteger(); Q_INVOKABLE QObject* constrainToPositiveInteger(); + Q_INVOKABLE QObject* translate(glm::vec3 translate); + Q_INVOKABLE QObject* transform(glm::mat4 transform); + Q_INVOKABLE QObject* postTransform(glm::mat4 transform); + Q_INVOKABLE QObject* rotate(glm::quat rotation); private: void to(const Endpoint::Pointer& destination); diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h index fd82821b3e..b06a43515f 100644 --- a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h @@ -21,6 +21,9 @@ public: virtual float apply(float value) const override { return glm::clamp(value, _min, _max); } + + virtual Pose apply(Pose value) const override { return value; } + virtual bool parseParameters(const QJsonValue& parameters) override; protected: float _min = 0.0f; diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h index 580dc2a856..c9a25fde72 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h @@ -22,6 +22,9 @@ public: virtual float apply(float value) const override { return glm::sign(value); } + + virtual Pose apply(Pose value) const override { return value; } + protected: }; diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h index 27395cde24..e3f4ee8929 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h @@ -22,6 +22,9 @@ public: virtual float apply(float value) const override { return (value <= 0.0f) ? 0.0f : 1.0f; } + + virtual Pose apply(Pose value) const override { return value; } + protected: }; diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h index 70ac657415..d898647126 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h @@ -20,6 +20,9 @@ public: DeadZoneFilter(float min = 0.0) : _min(min) {}; virtual float apply(float value) const override; + + virtual Pose apply(Pose value) const override { return value; } + virtual bool parseParameters(const QJsonValue& parameters) override; protected: float _min = 0.0f; diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h index 4f7e07928d..4eb563754f 100644 --- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h @@ -19,6 +19,9 @@ class HysteresisFilter : public Filter { public: HysteresisFilter(float min = 0.25, float max = 0.75); virtual float apply(float value) const override; + + virtual Pose apply(Pose value) const override { return value; } + virtual bool parseParameters(const QJsonValue& parameters) override; protected: float _min; diff --git a/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h new file mode 100644 index 0000000000..656a146ff2 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h @@ -0,0 +1,33 @@ +// +// Created by Brad Hefta-Gaub 2017/04/11 +// 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 +// + +#pragma once +#ifndef hifi_Controllers_Filters_PostTransform_h +#define hifi_Controllers_Filters_PostTransform_h + +#include + +#include "../Filter.h" + +namespace controller { + +class PostTransformFilter : public Filter { + REGISTER_FILTER_CLASS(PostTransformFilter); +public: + PostTransformFilter() { } + PostTransformFilter(glm::mat4 transform) : _transform(transform) {} + virtual float apply(float value) const override { return value; } + virtual Pose apply(Pose value) const override { return value.postTransform(_transform); } + virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); } +private: + glm::mat4 _transform; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h index 271f4a04f6..a8c7cbf9e6 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h @@ -23,6 +23,8 @@ public: virtual float apply(float value) const override; + virtual Pose apply(Pose value) const override { return value; } + virtual bool parseParameters(const QJsonValue& parameters) override; private: diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h new file mode 100644 index 0000000000..ee2e081393 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h @@ -0,0 +1,39 @@ +// +// Created by Brad Hefta-Gaub 2017/04/11 +// 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 +// + +#pragma once +#ifndef hifi_Controllers_Filters_Rotate_h +#define hifi_Controllers_Filters_Rotate_h + +#include + +#include "../Filter.h" + +namespace controller { + +class RotateFilter : public Filter { + REGISTER_FILTER_CLASS(RotateFilter); +public: + RotateFilter() { } + RotateFilter(glm::quat rotation) : _rotation(rotation) {} + + virtual float apply(float value) const override { return value; } + + virtual Pose apply(Pose value) const override { + return value.transform(glm::mat4(glm::quat(_rotation))); + } + + virtual bool parseParameters(const QJsonValue& parameters) override { return parseQuatParameter(parameters, _rotation); } + +private: + glm::quat _rotation; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h index 670da53fe8..7b03e2ce48 100644 --- a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h @@ -10,6 +10,8 @@ #ifndef hifi_Controllers_Filters_Scale_h #define hifi_Controllers_Filters_Scale_h +#include + #include "../Filter.h" namespace controller { @@ -23,6 +25,11 @@ public: virtual float apply(float value) const override { return value * _scale; } + + virtual Pose apply(Pose value) const override { + return value.transform(glm::scale(glm::mat4(), glm::vec3(_scale))); + } + virtual bool parseParameters(const QJsonValue& parameters) override; private: diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h new file mode 100644 index 0000000000..263b70c9b4 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h @@ -0,0 +1,35 @@ +// +// Created by Brad Hefta-Gaub 2017/04/11 +// 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 +// + +#pragma once +#ifndef hifi_Controllers_Filters_Transform_h +#define hifi_Controllers_Filters_Transform_h + +#include + +#include "../Filter.h" + +namespace controller { + +class TransformFilter : public Filter { + REGISTER_FILTER_CLASS(TransformFilter); +public: + TransformFilter() { } + TransformFilter(glm::mat4 transform) : _transform(transform) {} + + virtual float apply(float value) const override { return value; } + virtual Pose apply(Pose value) const override { return value.transform(_transform); } + virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); } + +private: + glm::mat4 _transform; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h new file mode 100644 index 0000000000..eda2912a8a --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h @@ -0,0 +1,35 @@ +// +// Created by Brad Hefta-Gaub 2017/04/11 +// 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 +// + +#pragma once +#ifndef hifi_Controllers_Filters_Translate_h +#define hifi_Controllers_Filters_Translate_h + +#include + +#include "../Filter.h" + +namespace controller { + +class TranslateFilter : public Filter { + REGISTER_FILTER_CLASS(TranslateFilter); +public: + TranslateFilter() { } + TranslateFilter(glm::vec3 translate) : _translate(translate) {} + + virtual float apply(float value) const override { return value; } + virtual Pose apply(Pose value) const override { return value.transform(glm::translate(_translate)); } + virtual bool parseParameters(const QJsonValue& parameters) override { return parseVec3Parameter(parameters, _translate); } + +private: + glm::vec3 _translate { 0.0f }; +}; + +} + +#endif diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 1d258560c3..4230f636b3 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -170,7 +170,7 @@ protected: bool _hasInternalKinematicChanges { false }; }; -typedef QSet SetOfMotionStates; -typedef QVector VectorOfMotionStates; +using SetOfMotionStates = QSet; +using VectorOfMotionStates = QVector; #endif // hifi_ObjectMotionState_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index ca6889485a..87a15eb264 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -220,6 +220,7 @@ void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) { body->setMotionState(nullptr); delete body; } + object->clearIncomingDirtyFlags(); } } diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 9f2f1aff5c..07de0e7b5c 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -41,8 +41,8 @@ public: void* _b; // ObjectMotionState pointer }; -typedef std::map ContactMap; -typedef std::vector CollisionEvents; +using ContactMap = std::map; +using CollisionEvents = std::vector; class PhysicsEngine { public: diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 10a9a20287..f44d736e1a 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -130,8 +130,8 @@ public: bool _enableTexturing { true }; RenderDetails _details; - render::ScenePointer _scene; // HACK - int8_t _cameraMode { -1 }; // HACK + render::ScenePointer _scene; + int8_t _cameraMode { -1 }; }; #endif // hifi_RenderArgs_h diff --git a/script-archive/controllers/puppetFeet3.js b/script-archive/controllers/puppetFeet3.js new file mode 100644 index 0000000000..3c9618edd9 --- /dev/null +++ b/script-archive/controllers/puppetFeet3.js @@ -0,0 +1,34 @@ +// +// puppetFeet3.js +// examples/controllers +// +// Created by Brad Hefta-Gaub on 2017/04/11 +// 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 +// + + +var MAPPING_NAME = "com.highfidelity.examples.puppetFeet3"; +var mapping = Controller.newMapping(MAPPING_NAME); +var puppetOffset = { x: 0, y: -1, z: 0 }; + +var rotation = Quat.fromPitchYawRollDegrees(0, 0, -90); +var noTranslation = { x: 0, y: 0, z: 0 }; +var transformMatrix = Mat4.createFromRotAndTrans(rotation, noTranslation); +var rotateAndTranslate = Mat4.createFromRotAndTrans(rotation, puppetOffset); + + +mapping.from(Controller.Standard.LeftHand).peek().rotate(rotation).translate(puppetOffset).to(Controller.Standard.LeftFoot); + +//mapping.from(Controller.Standard.LeftHand).peek().translate(puppetOffset).to(Controller.Standard.LeftFoot); +//mapping.from(Controller.Standard.LeftHand).peek().transform(transformMatrix).translate(puppetOffset).to(Controller.Standard.LeftFoot); +//mapping.from(Controller.Standard.LeftHand).peek().transform(rotateAndTranslate).to(Controller.Standard.LeftFoot); + +Controller.enableMapping(MAPPING_NAME); + + +Script.scriptEnding.connect(function(){ + mapping.disable(); +}); diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 12b91d372b..ccc9386d13 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -80,6 +80,9 @@ input[type=button].naked:active { #snapshot-images { width: 100%; + display: flex; + justify-content: center; + flex-direction: column; } #snapshot-images img { @@ -108,7 +111,7 @@ input[type=button].naked:active { justify-content: space-between; flex-direction: row; align-items: center; - height: 50px; + height: 45px; line-height: 60px; width: calc(100% - 8px); position: absolute; @@ -119,13 +122,13 @@ input[type=button].naked:active { .shareButtons { display: flex; align-items: center; - margin-left: 30px; + margin-left: 15px; height: 100%; - width: 80%; + width: 75%; } .blastToConnections { text-align: left; - margin-right: 25px; + margin-right: 20px; height: 29px; } .shareWithEveryone { @@ -158,10 +161,11 @@ input[type=button].naked:active { font-family: Raleway-SemiBold; font-size: 14px; color: white; - text-shadow: 2px 2px 3px #000000; height: 100%; margin-right: 10px; - width: 20%; +} +.showShareButtonsButtonDiv > label { + text-shadow: 2px 2px 3px #000000; } .showShareButton { width: 40px; @@ -193,23 +197,17 @@ input[type=button].naked:active { background-color: white; } .showShareButtonDots { - display: flex; - width: 32px; + display: block; + width: 40px; height: 40px; + font-family: HiFi-Glyphs; + font-size: 60px; position: absolute; - top: 5px; - right: 14px; + right: 20px; + bottom: 12px; + color: #00b4ef; pointer-events: none; } -.showShareButtonDots > span { - width: 10px; - height: 10px; - margin: auto; - background-color: #0093C5; - border-radius: 50%; - border-width: 0; - display: inline; -} /* // END styling of share overlay */ diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index f1ace02eb0..ac34cee09f 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -101,9 +101,11 @@ input[type=radio] { opacity: 0; } input[type=radio] + label{ - display: inline-block; - margin-left: -2em; - line-height: 2em; + display: inline-block; + margin-left: -2em; + line-height: 2em; + font-family: Raleway-SemiBold; + font-size: 14px; } input[type=radio] + label > span{ display: inline-block; @@ -157,7 +159,6 @@ input[type=radio]:active + label > span > span{ border-width: 0px; background-image: linear-gradient(#00B4EF, #1080B8); min-height: 30px; - } .blueButton:hover { background-image: linear-gradient(#00B4EF, #00B4EF); diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 53f4d17930..ed290e9756 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -52,7 +52,7 @@ function clearImages() { imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages, hifiShareButtonsDisabled) { +function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled) { if (!image_data.localPath) { return; } @@ -60,8 +60,9 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre // imageContainer setup var imageContainer = document.createElement("DIV"); imageContainer.id = id; - imageContainer.style.width = "100%"; - imageContainer.style.height = "251px"; + imageContainer.style.width = "95%"; + imageContainer.style.height = "240px"; + imageContainer.style.margin = "5px auto"; imageContainer.style.display = "flex"; imageContainer.style.justifyContent = "center"; imageContainer.style.alignItems = "center"; @@ -80,22 +81,22 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre if (isGif) { imageContainer.innerHTML += 'GIF'; } - if (!isGifLoading && !isShowingPreviousImages) { + if (!isGifLoading && !isShowingPreviousImages && canShare) { shareForUrl(id); - } else if (isShowingPreviousImages && canSharePreviousImages) { - appendShareBar(id, image_data.story_id, isGif, hifiShareButtonsDisabled) + } else if (isShowingPreviousImages && canShare && image_data.story_id) { + appendShareBar(id, image_data.story_id, isGif, blastButtonDisabled, hifiButtonDisabled) } } -function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { +function appendShareBar(divID, story_id, isGif, blastButtonDisabled, hifiButtonDisabled) { var story_url = "https://highfidelity.com/user_stories/" + story_id; var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); - document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled)); + document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, blastButtonDisabled, hifiButtonDisabled)); if (divID === "p0") { selectImageToShare(divID, true); } } -function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { +function createShareBar(parentID, isGif, shareURL, blastButtonDisabled, hifiButtonDisabled) { var shareBar = document.createElement("div"); shareBar.id = parentID + "shareBar"; shareBar.className = "shareControls"; @@ -109,8 +110,8 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var twitterButtonID = parentID + "twitterButton"; shareBar.innerHTML += '' + '' + @@ -118,7 +119,7 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { '' + '' + '
' + - '' + + '' + '
' + ''; @@ -149,6 +150,15 @@ function selectImageToShare(selectedID, isSelected) { shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; shareButtonsDiv.style.visibility = "visible"; + + var containers = document.getElementsByClassName("shareControls"); + var parentID; + for (var i = 0; i < containers.length; ++i) { + parentID = containers[i].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageToShare(parentID, false); + } + } } else { showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, true) }; showShareButtonsButton.classList.remove("active"); @@ -173,7 +183,6 @@ function blastToConnections(selectedID, isGif) { selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID document.getElementById(selectedID + "blastToConnectionsButton").disabled = true; - document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true; EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", @@ -185,7 +194,6 @@ function blastToConnections(selectedID, isGif) { function shareWithEveryone(selectedID, isGif) { selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID - document.getElementById(selectedID + "blastToConnectionsButton").disabled = true; document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true; EventBridge.emitWebEvent(JSON.stringify({ @@ -233,7 +241,7 @@ function handleCaptureSetting(setting) { window.onload = function () { // Uncomment the line below to test functionality in a browser. // See definition of "testInBrowser()" to modify tests. - //testInBrowser(true); + //testInBrowser(false); openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { @@ -260,7 +268,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, true, message.canShare, message.image_data[idx].buttonDisabled); + addImage(element, true, message.canShare, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled); }); break; case 'addImages': @@ -272,22 +280,24 @@ window.onload = function () { if (messageOptions.containsGif) { if (messageOptions.processingGif) { imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon - message.image_data.unshift({ localPath: messageOptions.loadingGifPath }); + message.image_data.push({ localPath: messageOptions.loadingGifPath }); message.image_data.forEach(function (element, idx, array) { - addImage(element, idx === 0, false, false); + addImage(element, idx === 1, idx === 0 && messageOptions.canShare, false); }); } else { var gifPath = message.image_data[0].localPath; - var p0img = document.getElementById('p0img'); - p0img.src = gifPath; + var p1img = document.getElementById('p1img'); + p1img.src = gifPath; - paths[0] = gifPath; - shareForUrl("p0"); + paths[1] = gifPath; + if (messageOptions.canShare) { + shareForUrl("p1"); + } } } else { imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, false, false, false); + addImage(element, false, messageOptions.canShare, false); }); } break; @@ -296,7 +306,7 @@ window.onload = function () { break; case 'snapshotUploadComplete': var isGif = message.image_url.split('.').pop().toLowerCase() === "gif"; - appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif); + appendShareBar(isGif ? "p1" : "p0", message.story_id, isGif); break; default: console.log("Unknown message action received in SnapshotReview.js."); @@ -329,6 +339,7 @@ function testInBrowser(isTestingSetupInstructions) { } else { imageCount = 1; //addImage({ localPath: 'http://lorempixel.com/553/255' }); - addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false); + addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-05-01_15-48-15.gif' }, false, true, true, false, false); + addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-05-01_15-48-15.jpg' }, false, true, true, false, false); } } diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 47082e882f..33d6b363ab 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -519,11 +519,9 @@ function updateTriggers(value, fromKeyboard, hand) { if (currentHand && hand !== currentHand) { - debug("currentHand", currentHand, "ignoring messages from", hand); + debug("currentHand", currentHand, "ignoring messages from", hand); // this can be a lot of spam on Touch. Should guard that someday. return; } - currentHand = hand; - currentHandJointIndex = getIdealHandJointIndex(MyAvatar, handToString(currentHand)); // Always, in case of changed skeleton. // ok now, we are either initiating or quitting... var isGripping = value > GRIP_MIN; if (isGripping) { @@ -531,6 +529,8 @@ if (state !== STATES.INACTIVE) { return; } + currentHand = hand; + currentHandJointIndex = getIdealHandJointIndex(MyAvatar, handToString(currentHand)); // Always, in case of changed skeleton. startHandshake(fromKeyboard); } else { // TODO: should we end handshake even when inactive? Ponder diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 1cc24b8265..d0d1bd96f5 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -117,13 +117,15 @@ function onMessage(message) { setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) })); if (Snapshot.getSnapshotsLocation() !== "") { - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "showPreviousImages", - options: snapshotOptions, - image_data: imageData, - canShare: !isDomainOpen(Settings.getValue("previousSnapshotDomainID")) - })); + isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "showPreviousImages", + options: snapshotOptions, + image_data: imageData, + canShare: canShare + })); + }); } else { tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", @@ -131,10 +133,12 @@ function onMessage(message) { })); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); - Settings.setValue("previousStillSnapSharingDisabled", false); + Settings.setValue("previousStillSnapBlastingDisabled", false); + Settings.setValue("previousStillSnapHifiSharingDisabled", false); Settings.setValue("previousAnimatedSnapPath", ""); Settings.setValue("previousAnimatedSnapStoryID", ""); - Settings.setValue("previousAnimatedSnapSharingDisabled", false); + Settings.setValue("previousAnimatedSnapBlastingDisabled", false); + Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); } break; case 'chooseSnapshotLocation': @@ -180,9 +184,9 @@ function onMessage(message) { isLoggedIn = Account.isLoggedIn(); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); if (message.isGif) { - Settings.setValue("previousAnimatedSnapSharingDisabled", true); + Settings.setValue("previousAnimatedSnapBlastingDisabled", true); } else { - Settings.setValue("previousStillSnapSharingDisabled", true); + Settings.setValue("previousStillSnapBlastingDisabled", true); } if (isLoggedIn) { @@ -220,9 +224,9 @@ function onMessage(message) { if (error || (response.status !== 'success')) { print("ERROR uploading announcement story: ", error || response.status); if (message.isGif) { - Settings.setValue("previousAnimatedSnapSharingDisabled", false); + Settings.setValue("previousAnimatedSnapBlastingDisabled", false); } else { - Settings.setValue("previousStillSnapSharingDisabled", false); + Settings.setValue("previousStillSnapBlastingDisabled", false); } return; } else { @@ -240,9 +244,9 @@ function onMessage(message) { isLoggedIn = Account.isLoggedIn(); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); if (message.isGif) { - Settings.setValue("previousAnimatedSnapSharingDisabled", true); + Settings.setValue("previousAnimatedSnapHifiSharingDisabled", true); } else { - Settings.setValue("previousStillSnapSharingDisabled", true); + Settings.setValue("previousStillSnapHifiSharingDisabled", true); } if (isLoggedIn) { @@ -264,9 +268,9 @@ function onMessage(message) { if (error || (response.status !== 'success')) { print("ERROR changing audience: ", error || response.status); if (message.isGif) { - Settings.setValue("previousAnimatedSnapSharingDisabled", false); + Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); } else { - Settings.setValue("previousStillSnapSharingDisabled", false); + Settings.setValue("previousStillSnapHifiSharingDisabled", false); } return; } else { @@ -301,21 +305,33 @@ function onButtonClicked() { shouldActivateButton = true; var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); - var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); + var previousStillSnapBlastingDisabled = Settings.getValue("previousStillSnapBlastingDisabled"); + var previousStillSnapHifiSharingDisabled = Settings.getValue("previousStillSnapHifiSharingDisabled"); var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); - var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); + var previousAnimatedSnapBlastingDisabled = Settings.getValue("previousAnimatedSnapBlastingDisabled"); + var previousAnimatedSnapHifiSharingDisabled = Settings.getValue("previousAnimatedSnapHifiSharingDisabled"); snapshotOptions = { containsGif: previousAnimatedSnapPath !== "", processingGif: false, shouldUpload: false } imageData = []; - if (previousAnimatedSnapPath !== "") { - imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); - } if (previousStillSnapPath !== "") { - imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); + imageData.push({ + localPath: previousStillSnapPath, + story_id: previousStillSnapStoryID, + blastButtonDisabled: previousStillSnapBlastingDisabled, + hifiButtonDisabled: previousStillSnapHifiSharingDisabled + }); + } + if (previousAnimatedSnapPath !== "") { + imageData.push({ + localPath: previousAnimatedSnapPath, + story_id: previousAnimatedSnapStoryID, + blastButtonDisabled: previousAnimatedSnapBlastingDisabled, + hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled + }); } tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); @@ -355,10 +371,12 @@ function takeSnapshot() { })); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); - Settings.setValue("previousStillSnapSharingDisabled", false); + Settings.setValue("previousStillSnapBlastingDisabled", false); + Settings.setValue("previousStillSnapHifiSharingDisabled", false); Settings.setValue("previousAnimatedSnapPath", ""); Settings.setValue("previousAnimatedSnapStoryID", ""); - Settings.setValue("previousAnimatedSnapSharingDisabled", false); + Settings.setValue("previousAnimatedSnapBlastingDisabled", false); + Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false); // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). @@ -377,6 +395,7 @@ function takeSnapshot() { resetOverlays = Menu.isOptionChecked("Overlays"); // For completeness. Certainly true if the button is visible to be clicked. reticleVisible = Reticle.visible; Reticle.visible = false; + Reticle.allowMouseCapture = false; var includeAnimated = Settings.getValue("alsoTakeAnimatedSnapshot", true); if (includeAnimated) { @@ -403,37 +422,40 @@ function takeSnapshot() { }, FINISH_SOUND_DELAY); } -function isDomainOpen(id) { +function isDomainOpen(id, callback) { print("Checking open status of domain with ID:", id); - if (!id) { - return false; + var status = false; + if (id) { + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=concurrency', + 'domain_id=' + id.slice(1, -1), + 'restriction=open,hifi' // If we're sharing, we're logged in + // If we're here, protocol matches, and it is online + ]; + var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&'); + + request({ + uri: url, + method: 'GET' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR getting open status of domain: ", error || response.status); + } else { + status = response.total_entries ? true : false; + } + print("Domain open status:", status); + callback(status); + }); + } else { + callback(status); } - - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=concurrency', - 'domain_id=' + id.slice(1, -1), - 'restriction=open,hifi' // If we're sharing, we're logged in - // If we're here, protocol matches, and it is online - ]; - var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&'); - - return request({ - uri: url, - method: 'GET' - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("ERROR getting open status of domain: ", error || response.status); - return false; - } else { - return response.total_entries; - } - }); } function stillSnapshotTaken(pathStillSnapshot, notify) { // show hud Reticle.visible = reticleVisible; + Reticle.allowMouseCapture = true; // show overlays if they were on if (resetOverlays) { Menu.setIsOptionChecked("Overlays", true); @@ -448,25 +470,27 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { // during which time the user may have moved. So stash that info in the dialog so that // it records the correct href. (We can also stash in .jpegs, but not .gifs.) // last element in data array tells dialog whether we can share or not - snapshotOptions = { - containsGif: false, - processingGif: false, - canShare: !isDomainOpen(domainId) - }; - imageData = [{ localPath: pathStillSnapshot, href: href }]; Settings.setValue("previousStillSnapPath", pathStillSnapshot); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "addImages", - options: snapshotOptions, - image_data: imageData - })); - if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } HMD.openTablet(); + + isDomainOpen(domainId, function (canShare) { + snapshotOptions = { + containsGif: false, + processingGif: false, + canShare: canShare + }; + imageData = [{ localPath: pathStillSnapshot, href: href }]; + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + }); } function processingGifStarted(pathStillSnapshot) { @@ -474,31 +498,33 @@ function processingGifStarted(pathStillSnapshot) { Window.processingGifCompleted.connect(processingGifCompleted); // show hud Reticle.visible = reticleVisible; + Reticle.allowMouseCapture = true; // show overlays if they were on if (resetOverlays) { Menu.setIsOptionChecked("Overlays", true); } - - snapshotOptions = { - containsGif: true, - processingGif: true, - loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !isDomainOpen(domainId) - }; - imageData = [{ localPath: pathStillSnapshot, href: href }]; Settings.setValue("previousStillSnapPath", pathStillSnapshot); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "addImages", - options: snapshotOptions, - image_data: imageData - })); - if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } HMD.openTablet(); + + isDomainOpen(domainId, function (canShare) { + snapshotOptions = { + containsGif: true, + processingGif: true, + loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), + canShare: canShare + }; + imageData = [{ localPath: pathStillSnapshot, href: href }]; + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + }); } function processingGifCompleted(pathAnimatedSnapshot) { @@ -508,20 +534,22 @@ function processingGifCompleted(pathAnimatedSnapshot) { buttonConnected = true; } - snapshotOptions = { - containsGif: true, - processingGif: false, - canShare: !isDomainOpen(domainId) - } - imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "addImages", - options: snapshotOptions, - image_data: imageData - })); + isDomainOpen(domainId, function (canShare) { + snapshotOptions = { + containsGif: true, + processingGif: false, + canShare: canShare + }; + imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + }); } function maybeDeleteSnapshotStories() { storyIDsToMaybeDelete.forEach(function (element, idx, array) { @@ -554,12 +582,21 @@ function onUsernameChanged() { shareAfterLogin = false; } } +function snapshotLocationSet(location) { + if (location !== "") { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "snapshotLocationChosen" + })); + } +} button.clicked.connect(onButtonClicked); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onUsernameChanged); +Snapshot.snapshotLocationSet.connect(snapshotLocationSet); Script.scriptEnding.connect(function () { if (buttonConnected) { button.clicked.disconnect(onButtonClicked); @@ -570,6 +607,7 @@ Script.scriptEnding.connect(function () { } Window.snapshotShared.disconnect(snapshotUploaded); tablet.screenChanged.disconnect(onTabletScreenChanged); + Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); }); }()); // END LOCAL_SCOPE