From 9ca27f267d542d5fbe4ead577d3f8c4a4a3259b4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 21 Jun 2016 16:48:04 -0700 Subject: [PATCH 01/36] cap lifetime rather than reject edits from nodes which only have tmp-rez rights --- .../resources/describe-settings.json | 6 +- libraries/entities/src/EntityTree.cpp | 61 +++++++++---------- libraries/entities/src/EntityTree.h | 1 - scripts/system/edit.js | 2 +- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 59fa9c4f3d..7375a0f650 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -384,7 +384,7 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which users or groups can have which domain-wide permissions.", + "help": "Indicate which users or groups can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, @@ -394,7 +394,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], @@ -463,7 +463,7 @@ "span": 1 }, { - "label": "Permissions ?", + "label": "Permissions ?", "span": 6 } ], diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ec1f8a50bc..820d97c915 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -148,11 +148,11 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return false; } - if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) { - // we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones - if (properties.lifetimeChanged()) { - qCDebug(entities) << "Refusing disallowed entity lifetime adjustment."; - return false; + if (!canRezPermanentEntities) { + // we don't allow a Node that can't create permanent entities to raise lifetimes on existing ones + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || properties.getLifetime() > _maxTmpEntityLifetime) { + qCDebug(entities) << "Capping disallowed entity lifetime adjustment."; + properties.setLifetime(_maxTmpEntityLifetime); } } @@ -321,26 +321,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return true; } -bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) { - float lifeTime = properties.getLifetime(); - - if (lifeTime == ENTITY_ITEM_IMMORTAL_LIFETIME || lifeTime > _maxTmpEntityLifetime) { - // this is an attempt to rez a permanent or non-temporary entity. - if (!canRez) { - return false; - } - } else { - // this is an attempt to rez a temporary entity. - if (!canRezTmp) { - return false; - } - } - - return true; -} - EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; + EntityItemProperties props = properties; auto nodeList = DependencyManager::get(); if (!nodeList) { @@ -348,16 +331,19 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti return nullptr; } - bool clientOnly = properties.getClientOnly(); + bool clientOnly = props.getClientOnly(); - if (!clientOnly && getIsClient() && - !permissionsAllowRez(properties, nodeList->getThisNodeCanRez(), nodeList->getThisNodeCanRezTmp())) { - // if our Node isn't allowed to create entities in this domain, don't try. - return nullptr; + if (!clientOnly && getIsClient() && !nodeList->getThisNodeCanRez() && nodeList->getThisNodeCanRezTmp()) { + // we are a client which is only allowed to rez temporary entities. cap the lifetime. + if (props.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { + props.setLifetime(_maxTmpEntityLifetime); + } else { + props.setLifetime(glm::min(props.getLifetime(), _maxTmpEntityLifetime)); + } } bool recordCreationTime = false; - if (properties.getCreated() == UNKNOWN_CREATED_TIME) { + if (props.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity // and we must record its creation time recordCreationTime = true; @@ -372,8 +358,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti } // construct the instance of the entity - EntityTypes::EntityType type = properties.getType(); - result = EntityTypes::constructEntityItem(type, entityID, properties); + EntityTypes::EntityType type = props.getType(); + result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { if (recordCreationTime) { @@ -922,11 +908,20 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c EntityItemID entityItemID; EntityItemProperties properties; startDecode = usecTimestampNow(); - + bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties); endDecode = usecTimestampNow(); + if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { + // this node is only allowed to rez temporary entities. cap the lifetime. + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { + properties.setLifetime(_maxTmpEntityLifetime); + } else { + properties.setLifetime(glm::min(properties.getLifetime(), _maxTmpEntityLifetime)); + } + } + // If we got a valid edit packet, then it could be a new entity or it could be an update to // an existing entity... handle appropriately if (validEditPacket) { @@ -955,7 +950,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endUpdate = usecTimestampNow(); _totalUpdates++; } else if (message.getType() == PacketType::EntityAdd) { - if (permissionsAllowRez(properties, senderNode->getCanRez(), senderNode->getCanRezTmp())) { + if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); startCreate = usecTimestampNow(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8afb8d878f..15daf3bf3c 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -64,7 +64,6 @@ public: void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; } - bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp); /// Implements our type specific root element factory virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 42eddf11c3..b439de2c9d 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -332,7 +332,7 @@ var toolBar = (function() { that.setActive = function(active) { if (active != isActive) { - if (active && !Entities.canAdjustLocks()) { + if (active && !Entities.canRez() && !Entities.canRezTmp()) { Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); } else { Messages.sendLocalMessage("edit-events", JSON.stringify({ From 2a82dddc2b019e53e2b6cfc1b9a7d9f2eb8932d7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 21 Jun 2016 17:36:36 -0700 Subject: [PATCH 02/36] Draw attach points as if they were equip-hotspots --- .../system/controllers/handControllerGrab.js | 17 ++++++-- scripts/system/libraries/Xform.js | 40 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 scripts/system/libraries/Xform.js diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 39d85f1224..31f8ebd73f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,9 +10,10 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ Script.include("/~/system/libraries/utils.js"); +Script.include("../libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -879,21 +880,29 @@ function MyController(hand) { var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); var i, l = entities.length; for (i = 0; i < l; i++) { - var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); + var props = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); if (wearableData && wearableData.joints) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; if (wearableData.joints[handJointName]) { + var handOffsetPos = wearableData.joints[handJointName][0]; + var handOffsetRot = wearableData.joints[handJointName][1]; + + var handOffsetXform = new Xform(handOffsetRot, handOffsetPos); + var objectXform = new Xform(props.rotation, props.position); + var overlayXform = Xform.mul(objectXform, handOffsetXform.inv()); + // draw the hotspot this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: grabProps.position, + rotation: overlayXform.rot, + position: overlayXform.pos, size: 0.2, color: { red: 90, green: 255, blue: 90 }, alpha: 0.7, solid: true, visible: true, - ignoreRayIntersection: false, + ignoreRayIntersection: true, drawInFront: false })); } diff --git a/scripts/system/libraries/Xform.js b/scripts/system/libraries/Xform.js new file mode 100644 index 0000000000..75051c4983 --- /dev/null +++ b/scripts/system/libraries/Xform.js @@ -0,0 +1,40 @@ +// +// Created by Anthony J. Thibault on 2016/06/21 +// Copyright 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 +// + +// ctor +Xform = function(rot, pos) { + this.rot = rot; + this.pos = pos; +} + +Xform.ident = function() { + return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0}); +}; + +Xform.mul = function(lhs, rhs) { + var rot = Quat.multiply(lhs.rot, rhs.rot); + var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos)); + return new Xform(rot, pos); +}; + +Xform.prototype.inv = function() { + var invRot = Quat.inverse(this.rot); + var invPos = Vec3.multiply(-1, this.pos); + return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos)); +}; + +Xform.prototype.mirrorX = function() { + return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w}, + {x: -this.pos.x, y: this.pos.y, z: this.pos.z}); +}; + +Xform.prototype.toString = function() { + var rot = this.rot; + var pos = this.pos; + return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")"; +}; From c62b73bf61e14d745f9ce15125999442c9578d2c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 22 Jun 2016 11:42:57 -0700 Subject: [PATCH 03/36] when the entity-server caps a lifetime of an edit from a tmp-rez only interface, bump the lastEdited time forward so that the interface accepts the change back into its local tree --- libraries/entities/src/EntityTree.cpp | 40 ++++++++++----------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 820d97c915..ad0f929e59 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -130,16 +130,13 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemProperties properties = origProperties; bool allowLockChange; - bool canRezPermanentEntities; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get(); allowLockChange = nodeList->isAllowedEditor(); - canRezPermanentEntities = nodeList->getThisNodeCanRez(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->isAllowedEditor(); - canRezPermanentEntities = senderNode->getCanRez(); senderID = senderNode->getUUID(); } @@ -148,14 +145,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI return false; } - if (!canRezPermanentEntities) { - // we don't allow a Node that can't create permanent entities to raise lifetimes on existing ones - if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || properties.getLifetime() > _maxTmpEntityLifetime) { - qCDebug(entities) << "Capping disallowed entity lifetime adjustment."; - properties.setLifetime(_maxTmpEntityLifetime); - } - } - // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { @@ -331,17 +320,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti return nullptr; } - bool clientOnly = props.getClientOnly(); - - if (!clientOnly && getIsClient() && !nodeList->getThisNodeCanRez() && nodeList->getThisNodeCanRezTmp()) { - // we are a client which is only allowed to rez temporary entities. cap the lifetime. - if (props.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { - props.setLifetime(_maxTmpEntityLifetime); - } else { - props.setLifetime(glm::min(props.getLifetime(), _maxTmpEntityLifetime)); - } - } - bool recordCreationTime = false; if (props.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity @@ -876,6 +854,13 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList= 0) { + float value = properties.getLifetime(); + changedProperties[index] = QString("lifetime:") + QString::number((int)value); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, @@ -913,12 +898,15 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c entityItemID, properties); endDecode = usecTimestampNow(); + const quint64 LAST_EDITED_SERVERSIDE_BUMP = 10000; // usec if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { - // this node is only allowed to rez temporary entities. cap the lifetime. - if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME) { + // this node is only allowed to rez temporary entities. if need be, cap the lifetime. + if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || + properties.getLifetime() > _maxTmpEntityLifetime) { properties.setLifetime(_maxTmpEntityLifetime); - } else { - properties.setLifetime(glm::min(properties.getLifetime(), _maxTmpEntityLifetime)); + // also bump up the lastEdited time of the properties so that the interface that created this edit + // will accept our adjustment to lifetime back into its own entity-tree. + properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP); } } From b6b73af2b487b10e9cabe85299f498ef0b683df2 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 14:27:45 -0700 Subject: [PATCH 04/36] Clean up domain-server setup --- domain-server/src/DomainServer.cpp | 70 +++++++++++++----------------- domain-server/src/DomainServer.h | 2 +- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 223cab61da..a4b57226ed 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -104,23 +104,25 @@ DomainServer::DomainServer(int argc, char* argv[]) : connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, &_gatekeeper, &DomainGatekeeper::updateNodePermissions); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { - // we either read a certificate and private key or were not passed one - // and completed login or did not need to - - qDebug() << "Setting up LimitedNodeList and assignments."; - setupNodeListAndAssignments(); - - // setup automatic networking settings with data server - setupAutomaticNetworking(); - - // preload some user public keys so they can connect on first request - _gatekeeper.preloadAllowedUserPublicKeys(); - - optionallyGetTemporaryName(args); + // if we were given a certificate/private key or oauth credentials they must succeed + if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { + return; } + qDebug() << "Setting up domain-server"; + setupNodeListAndAssignments(); + setupAutomaticNetworking(); + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + _metadata = new DomainMetadata(this); + + // check for the temporary name parameter + const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; + if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { + getTemporaryName(); + } + + qDebug() << "domain-server" << nullptr << "is running"; } DomainServer::~DomainServer() { @@ -233,34 +235,25 @@ bool DomainServer::optionallySetupOAuth() { static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; -void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { - // check for the temporary name parameter - const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; +void DomainServer::getTemporaryName(bool force) { + // check if we already have a domain ID + const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); - if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) { - - // make sure we don't already have a domain ID - const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); - if (idValueVariant) { - qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings." - << "Will not request temporary name."; + if (idValueVariant) { + qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."; + if (force) { + qWarning() << "Temporary domain name will be requested to replace it."; + } else { + qWarning() << "Temporary domain name will not be requested."; return; } - - // we've been asked to grab a temporary name from the API - // so fire off that request now - auto accountManager = DependencyManager::get(); - - // get callbacks for temporary domain result - JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; - callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess"; - callbackParameters.errorCallbackReceiver = this; - callbackParameters.errorCallbackMethod = "handleTempDomainError"; - - accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, - QNetworkAccessManager::PostOperation, callbackParameters); } + + // request a temporary name from the metaverse + auto accountManager = DependencyManager::get(); + JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" }; + accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParameters); } void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { @@ -333,7 +326,6 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c742dbc9b3..8a25591605 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -100,7 +100,7 @@ private: bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - void optionallyGetTemporaryName(const QStringList& arguments); + void getTemporaryName(bool force = false); static bool packetVersionMatch(const udt::Packet& packet); From 3a36b0a2e5307094c106c73d4ddfb3cb5a289838 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 18:31:23 -0700 Subject: [PATCH 05/36] add temporary domain info to account info --- libraries/networking/src/AccountManager.cpp | 5 +++++ libraries/networking/src/AccountManager.h | 3 +++ libraries/networking/src/DataServerAccountInfo.cpp | 13 ++++++++++--- libraries/networking/src/DataServerAccountInfo.h | 8 +++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 26b3801ec1..ac6d0cd4a0 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -471,6 +471,11 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) persistAccountToFile(); } +void AccountManager::setTemporaryDomain(const QUuid& domainID, const QString& key) { + _accountInfo.setTemporaryDomain(domainID, key); + persistAccountToFile(); +} + void AccountManager::requestAccessToken(const QString& login, const QString& password) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 4803d2625f..dc3315eb45 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -88,6 +88,9 @@ public: void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } + void setTemporaryDomain(const QUuid& domainID, const QString& key); + const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } + public slots: void requestAccessToken(const QString& login, const QString& password); diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 211bfdccfa..6c6f3eb90c 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -25,6 +25,8 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif +const QString DataServerAccountInfo::EMPTY_KEY = QString(); + DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() { _accessToken = otherInfo._accessToken; _username = otherInfo._username; @@ -33,6 +35,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _walletID = otherInfo._walletID; _privateKey = otherInfo._privateKey; _domainID = otherInfo._domainID; + _temporaryDomainID = otherInfo._temporaryDomainID; + _temporaryDomainApiKey = otherInfo._temporaryDomainApiKey; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -51,6 +55,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_walletID, otherInfo._walletID); swap(_privateKey, otherInfo._privateKey); swap(_domainID, otherInfo._domainID); + swap(_temporaryDomainID, otherInfo._temporaryDomainID); + swap(_temporaryDomainApiKey, otherInfo._temporaryDomainApiKey); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -145,13 +151,14 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) { QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey - << info._walletID << info._privateKey << info._domainID; - + << info._walletID << info._privateKey << info._domainID + << info._temporaryDomainID << info._temporaryDomainApiKey; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey - >> info._walletID >> info._privateKey >> info._domainID; + >> info._walletID >> info._privateKey >> info._domainID + >> info._temporaryDomainID >> info._temporaryDomainApiKey; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 6ee726efde..8cb416cf34 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -22,6 +22,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f; class DataServerAccountInfo : public QObject { Q_OBJECT + const static QString EMPTY_KEY; public: DataServerAccountInfo() {}; DataServerAccountInfo(const DataServerAccountInfo& otherInfo); @@ -52,6 +53,9 @@ public: void setDomainID(const QUuid& domainID) { _domainID = domainID; } const QUuid& getDomainID() const { return _domainID; } + void setTemporaryDomain(const QUuid& domainID, const QString& key) { _temporaryDomainID = domainID; _temporaryDomainApiKey = key; } + const QString& getTemporaryDomainKey(const QUuid& domainID) { return domainID == _temporaryDomainID ? _temporaryDomainApiKey : EMPTY_KEY; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -67,7 +71,9 @@ private: QString _xmppPassword; QString _discourseApiKey; QUuid _walletID; - QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain + QUuid _domainID; + QUuid _temporaryDomainID; + QString _temporaryDomainApiKey; QByteArray _privateKey; }; From eebf8e91c6bdcca2308e64b75a4b8ee97b1b970b Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 19:02:27 -0700 Subject: [PATCH 06/36] add api_key to domains/public_key updates --- libraries/networking/src/AccountManager.cpp | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index ac6d0cd4a0..38f33da6ce 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -652,22 +652,33 @@ void AccountManager::processGeneratedKeypair() { const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; QString uploadPath; - if (keypairGenerator->getDomainID().isNull()) { + const auto& domainID = keypairGenerator->getDomainID(); + if (domainID.isNull()) { uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; } else { - uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID())); + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID)); } // setup a multipart upload to send up the public key QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart keyPart; - keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - keyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - keyPart.setBody(keypairGenerator->getPublicKey()); + QHttpPart publicKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - requestMultiPart->append(keyPart); + publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + publicKeyPart.setBody(keypairGenerator->getPublicKey()); + requestMultiPart->append(publicKeyPart); + + if (!domainID.isNull()) { + const auto& key = getTemporaryDomainKey(domainID); + QHttpPart apiKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"api_key\"")); + apiKeyPart.setBody(key.toUtf8()); + requestMultiPart->append(apiKeyPart); + } // setup callback parameters so we know once the keypair upload has succeeded or failed JSONCallbackParameters callbackParameters; From a6115cba6e981f17477edd98a0b6695770cc5c19 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 19:04:03 -0700 Subject: [PATCH 07/36] update temporary domains to use api_key --- domain-server/src/DomainServer.cpp | 149 +++++++++++++++++++---------- domain-server/src/DomainServer.h | 10 +- 2 files changed, 105 insertions(+), 54 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a4b57226ed..df1697af28 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -76,6 +76,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : setApplicationVersion(BuildInfo::VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); + qDebug() << "Setting up domain-server"; + // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set(); @@ -109,20 +111,26 @@ DomainServer::DomainServer(int argc, char* argv[]) : return; } - qDebug() << "Setting up domain-server"; - setupNodeListAndAssignments(); - setupAutomaticNetworking(); - _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request - - _metadata = new DomainMetadata(this); - // check for the temporary name parameter const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { getTemporaryName(); } - qDebug() << "domain-server" << nullptr << "is running"; + setupNodeListAndAssignments(); + setupAutomaticNetworking(); + if (!getID().isNull()) { + setupHeartbeatToMetaverse(); + // send the first heartbeat immediately + sendHeartbeatToMetaverse(); + } + + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + + _metadata = new DomainMetadata(this); + + + qDebug() << "domain-server is running"; } DomainServer::~DomainServer() { @@ -150,6 +158,10 @@ void DomainServer::restart() { exit(DomainServer::EXIT_CODE_REBOOT); } +const QUuid& DomainServer::getID() { + return DependencyManager::get()->getSessionUUID(); +} + bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_PRIVATE_KEY_OPTION = "key"; @@ -264,11 +276,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { static const QString DOMAIN_KEY = "domain"; static const QString ID_KEY = "id"; static const QString NAME_KEY = "name"; + static const QString KEY_KEY = "api_key"; auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); if (!domainObject.isEmpty()) { auto id = domainObject[ID_KEY].toString(); auto name = domainObject[NAME_KEY].toString(); + auto key = domainObject[KEY_KEY].toString(); qInfo() << "Received new temporary domain name" << name; qDebug() << "The temporary domain ID is" << id; @@ -284,9 +298,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { // change our domain ID immediately DependencyManager::get()->setSessionUUID(QUuid { id }); - // change our automatic networking settings so that we're communicating with the ICE server - setupICEHeartbeatForFullNetworking(); + // store the new token to the account info + auto accountManager = DependencyManager::get(); + accountManager->setTemporaryDomain(id, key); + // update our heartbeats to use the correct id + setupICEHeartbeatForFullNetworking(); + setupHeartbeatToMetaverse(); } else { qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again" << "via domain-server relaunch or from the domain-server settings."; @@ -325,7 +343,7 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { } -void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { +void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); @@ -450,29 +468,20 @@ bool DomainServer::resetAccountManagerAccessToken() { } void DomainServer::setupAutomaticNetworking() { - auto nodeList = DependencyManager::get(); - + qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); + auto nodeList = DependencyManager::get(); + const QUuid& domainID = getID(); + if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { setupICEHeartbeatForFullNetworking(); } - _hasAccessToken = resetAccountManagerAccessToken(); - - if (!_hasAccessToken) { - qDebug() << "Will not send heartbeat to Metaverse API without an access token."; - qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface."; - - return; - } - if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { - const QUuid& domainID = nodeList->getSessionUUID(); - if (!domainID.isNull()) { qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); @@ -484,9 +493,6 @@ void DomainServer::setupAutomaticNetworking() { // have the LNL enable public socket updating via STUN nodeList->startSTUNPublicSocketUpdate(); - } else { - // send our heartbeat to data server so it knows what our network settings are - sendHeartbeatToMetaverse(); } } else { qDebug() << "Cannot enable domain-server automatic networking without a domain ID." @@ -494,18 +500,20 @@ void DomainServer::setupAutomaticNetworking() { return; } - } else { - sendHeartbeatToMetaverse(); } +} - qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; - - // no matter the auto networking settings we should heartbeat to the data-server every 15s +void DomainServer::setupHeartbeatToMetaverse() { + // heartbeat to the data-server every 15s const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000; - QTimer* dataHeartbeatTimer = new QTimer(this); - connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse())); - dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); + if (!_metaverseHeartbeatTimer) { + // setup a timer to heartbeat with the metaverse-server + _metaverseHeartbeatTimer = new QTimer { this }; + connect(_metaverseHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse())); + // do not send a heartbeat immediately - this avoids flooding if the heartbeat fails with a 401 + _metaverseHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); + } } void DomainServer::setupICEHeartbeatForFullNetworking() { @@ -524,22 +532,21 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { limitedNodeList->startSTUNPublicSocketUpdate(); // to send ICE heartbeats we'd better have a private key locally with an uploaded public key - auto accountManager = DependencyManager::get(); - auto domainID = accountManager->getAccountInfo().getDomainID(); - // if we have an access token and we don't have a private key or the current domain ID has changed // we should generate a new keypair - if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { - accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + auto accountManager = DependencyManager::get(); + if (!accountManager->getAccountInfo().hasPrivateKey() || accountManager->getAccountInfo().getDomainID() != getID()) { + accountManager->generateNewDomainKeypair(getID()); } // hookup to the signal from account manager that tells us when keypair is available connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); if (!_iceHeartbeatTimer) { - // setup a timer to heartbeat with the ice-server every so often + // setup a timer to heartbeat with the ice-server _iceHeartbeatTimer = new QTimer { this }; connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); + sendHeartbeatToIceServer(); _iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); } } @@ -1067,9 +1074,6 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) } void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { - auto nodeList = DependencyManager::get(); - const QUuid& domainID = nodeList->getSessionUUID(); - // Setup the domain object to send to the data server QJsonObject domainObject; @@ -1088,6 +1092,13 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; + const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); + if (!temporaryDomainKey.isEmpty()) { + // add the temporary domain token + const QString KEY_KEY = "api_key"; + domainObject[KEY_KEY] = temporaryDomainKey; + } + if (_metadata) { // Add the metadata to the heartbeat static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; @@ -1097,18 +1108,47 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact))); static const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; - DependencyManager::get()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), - AccountManagerAuth::Required, + DependencyManager::get()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())), + AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), + JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"), domainUpdateJSON.toUtf8()); } +void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { + if (!_metaverseHeartbeatTimer) { + // avoid rehandling errors from the same issue + return; + } + + // if we have a temporary domain with a bad token, we will get a 401 + if (requestReply.error() == QNetworkReply::NetworkError::AuthenticationRequiredError) { + static const QString DATA_KEY = "data"; + static const QString TOKEN_KEY = "api_key"; + + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; + + if (!tokenFailure.isNull()) { + qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; + + // halt heartbeats until we have a token + _metaverseHeartbeatTimer->deleteLater(); + _metaverseHeartbeatTimer = nullptr; + + // give up eventually to avoid flooding traffic + static const int MAX_ATTEMPTS = 5; + static int attempt = 0; + if (++attempt < MAX_ATTEMPTS) { + // get a new temporary name and token + getTemporaryName(true); + } + } + } +} + void DomainServer::sendICEServerAddressToMetaverseAPI() { if (!_iceServerSocket.isNull()) { - auto nodeList = DependencyManager::get(); - const QUuid& domainID = nodeList->getSessionUUID(); - const QString ICE_SERVER_ADDRESS = "ice_server_address"; QJsonObject domainObject; @@ -1116,6 +1156,13 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { // we're using full automatic networking and we have a current ice-server socket, use that now domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString(); + const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); + if (!temporaryDomainKey.isEmpty()) { + // add the temporary domain token + const QString KEY_KEY = "api_key"; + domainObject[KEY_KEY] = temporaryDomainKey; + } + QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); // make sure we hear about failure so we can retry @@ -1127,7 +1174,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address"; - DependencyManager::get()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + DependencyManager::get()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())), AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 8a25591605..138cb9ca2d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -80,6 +80,8 @@ private slots: void handleTempDomainSuccess(QNetworkReply& requestReply); void handleTempDomainError(QNetworkReply& requestReply); + void handleMetaverseHeartbeatError(QNetworkReply& requestReply); + void queuedQuit(QString quitMessage, int exitCode); void handleKeypairChange(); @@ -96,7 +98,9 @@ signals: void userDisconnected(); private: - void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + const QUuid& getID(); + + void setupNodeListAndAssignments(); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); @@ -108,6 +112,7 @@ private: void setupAutomaticNetworking(); void setupICEHeartbeatForFullNetworking(); + void setupHeartbeatToMetaverse(); void sendHeartbeatToMetaverse(const QString& networkAddress); void randomizeICEServerAddress(bool shouldTriggerHostLookup); @@ -178,6 +183,7 @@ private: // These will be parented to this, they are not dangling DomainMetadata* _metadata { nullptr }; QTimer* _iceHeartbeatTimer { nullptr }; + QTimer* _metaverseHeartbeatTimer { nullptr }; QList _iceServerAddresses; QSet _failedIceServerAddresses; @@ -186,8 +192,6 @@ private: int _numHeartbeatDenials { 0 }; bool _connectedToICEServer { false }; - bool _hasAccessToken { false }; - friend class DomainGatekeeper; friend class DomainMetadata; }; From 4a30d549add061e408c95d692e2098ef6eb70207 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 17 Jun 2016 19:26:22 -0700 Subject: [PATCH 08/36] force temp domain reset on 404 too (401 already) --- domain-server/src/DomainServer.cpp | 51 +++++++++++++++++++----------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index df1697af28..192c0d26e6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1121,29 +1121,42 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { return; } - // if we have a temporary domain with a bad token, we will get a 401 - if (requestReply.error() == QNetworkReply::NetworkError::AuthenticationRequiredError) { - static const QString DATA_KEY = "data"; - static const QString TOKEN_KEY = "api_key"; + // check if we need to force a new temporary domain name + switch (requestReply.error()) { + // if we have a temporary domain with a bad token, we get a 401 + case QNetworkReply::NetworkError::AuthenticationRequiredError: { + static const QString DATA_KEY = "data"; + static const QString TOKEN_KEY = "api_key"; - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); - auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; - if (!tokenFailure.isNull()) { - qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; - - // halt heartbeats until we have a token - _metaverseHeartbeatTimer->deleteLater(); - _metaverseHeartbeatTimer = nullptr; - - // give up eventually to avoid flooding traffic - static const int MAX_ATTEMPTS = 5; - static int attempt = 0; - if (++attempt < MAX_ATTEMPTS) { - // get a new temporary name and token - getTemporaryName(true); + if (!tokenFailure.isNull()) { + qWarning() << "Temporary domain name lacks a valid API key, and is being reset."; } + break; } + // if the domain does not (or no longer) exists, we get a 404 + case QNetworkReply::NetworkError::ContentNotFoundError: + qWarning() << "Domain not found, getting a new temporary domain."; + break; + // otherwise, we erred on something else, and should not force a temporary domain + default: + return; + } + + // halt heartbeats until we have a token + _metaverseHeartbeatTimer->deleteLater(); + _metaverseHeartbeatTimer = nullptr; + + // give up eventually to avoid flooding traffic + static const int MAX_ATTEMPTS = 5; + static int attempt = 0; + if (++attempt < MAX_ATTEMPTS) { + // get a new temporary name and token + getTemporaryName(true); + } else { + qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart."; } } From f495f5e3b56173d15977159300db9802fa46843d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 16:05:55 -0700 Subject: [PATCH 09/36] removed mac hydra support --- plugins/hifiSixense/src/SixenseManager.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 03028249a3..48d13a8eaf 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -66,14 +66,8 @@ const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data"; const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data"; bool SixenseManager::isSupported() const { -#ifdef HAVE_SIXENSE - -#if defined(Q_OS_OSX) - return QSysInfo::macVersion() <= QSysInfo::MV_MAVERICKS; -#else +#if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX) return true; -#endif - #else return false; #endif From 3c9f3f392736b56a0641a6f6707c00077b2871bf Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Wed, 22 Jun 2016 16:22:14 -0700 Subject: [PATCH 10/36] Automatically enter first person when in HMD mode --- interface/src/Application.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d20a62fc7b..ddfa5dae8b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5350,7 +5350,13 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + // go into first person when they are in HMD mode, since 3rd person HMD is dumb + if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From 7e5b9db13a688100d9d587c1a4147e6a65f15e78 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Wed, 22 Jun 2016 16:50:47 -0700 Subject: [PATCH 11/36] Uses spaces, not tabs --- interface/src/Application.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ddfa5dae8b..53b9c18360 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5349,14 +5349,14 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - - // go into first person when they are in HMD mode, since 3rd person HMD is dumb - if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { - menu->setIsOptionChecked(MenuOption::FirstPerson, true); - cameraMenuChanged(); - } - - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + + // go into first person when they are in HMD mode, since 3rd person HMD is dumb + if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From a9ed0b1c8386c530e08ef9e23845ca21580f05f7 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 16:56:50 -0700 Subject: [PATCH 12/36] completely destroyed sixense on macs --- cmake/externals/sixense/CMakeLists.txt | 25 +-- cmake/macros/TargetSixense.cmake | 12 +- plugins/hifiSixense/src/SixenseManager.cpp | 2 + plugins/hifiSixense/src/SixenseSupportOSX.cpp | 155 ------------------ 4 files changed, 10 insertions(+), 184 deletions(-) delete mode 100644 plugins/hifiSixense/src/SixenseSupportOSX.cpp diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt index 16f2850449..bd0d042c0b 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/sixense/CMakeLists.txt @@ -57,30 +57,7 @@ if (WIN32) elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/lib/osx_x64/debug_dll/libsixensed_x64.dylib CACHE TYPE INTERNAL) - - set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64") - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-release - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/release_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) - - set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64") - ExternalProject_Add_Step( - ${EXTERNAL_NAME} - change-install-name-debug - COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/debug_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install - WORKING_DIRECTORY - LOG 1 - ) + # We no longer support Sixense on Macs due to bugs in the Sixense DLL elseif(NOT ANDROID) diff --git a/cmake/macros/TargetSixense.cmake b/cmake/macros/TargetSixense.cmake index 6fd9cede1f..28128d8b79 100644 --- a/cmake/macros/TargetSixense.cmake +++ b/cmake/macros/TargetSixense.cmake @@ -6,9 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_SIXENSE) - add_dependency_external_projects(sixense) - find_package(Sixense REQUIRED) - target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) - add_definitions(-DHAVE_SIXENSE) + if(NOT APPLE) + add_dependency_external_projects(sixense) + find_package(Sixense REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) + add_definitions(-DHAVE_SIXENSE) + endif() endmacro() diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 48d13a8eaf..ade643ec72 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -131,6 +131,7 @@ void SixenseManager::setSixenseFilter(bool filter) { void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED +#ifdef HAVE_SIXENSE static bool sixenseHasBeenConnected { false }; if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) { sixenseHasBeenConnected = true; @@ -146,6 +147,7 @@ void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibr _container->requestReset(); _inputDevice->_requestReset = false; } +#endif } void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { diff --git a/plugins/hifiSixense/src/SixenseSupportOSX.cpp b/plugins/hifiSixense/src/SixenseSupportOSX.cpp deleted file mode 100644 index fce2ea023b..0000000000 --- a/plugins/hifiSixense/src/SixenseSupportOSX.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// -// SixenseSupportOSX.cpp -// libraries/input-plugins/src/input-plugins -// -// Created by Clement on 10/20/15. -// Copyright 2015 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 -// - -// Mock implementation of sixense.h to hide dynamic linking on OS X -#if defined(__APPLE__) && defined(HAVE_SIXENSE) -#include - -#include - -#include -#include -#include - -#ifndef SIXENSE_LIB_FILENAME -#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64" -#endif - -using Library = std::unique_ptr; -static Library SIXENSE; - -struct Callable { - template - int operator() (Args&&... args){ - return reinterpret_cast(function)(std::forward(args)...); - } - QFunctionPointer function; -}; - -Callable resolve(const Library& library, const char* name) { - Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded"); - auto function = library->resolve(name); - Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str()); - return Callable { function }; -} -#define FORWARD resolve(SIXENSE, __FUNCTION__) - - -void loadSixense() { - Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded"); - SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME)); - Q_CHECK_PTR(SIXENSE); - - if (SIXENSE->load()){ - qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName(); - } else { - qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString(); - qDebug() << "Continuing without hydra support."; - } -} -void unloadSixense() { - SIXENSE->unload(); -} - - -// sixense.h wrapper for OSX dynamic linking -int sixenseInit() { - loadSixense(); - if (!SIXENSE || !SIXENSE->isLoaded()) { - return SIXENSE_FAILURE; - } - return FORWARD(); -} -int sixenseExit() { - auto returnCode = FORWARD(); - unloadSixense(); - return returnCode; -} - -int sixenseGetMaxBases() { - return FORWARD(); -} -int sixenseSetActiveBase(int i) { - return FORWARD(i); -} -int sixenseIsBaseConnected(int i) { - return FORWARD(i); -} - -int sixenseGetMaxControllers() { - return FORWARD(); -} -int sixenseIsControllerEnabled(int which) { - return FORWARD(which); -} -int sixenseGetNumActiveControllers() { - return FORWARD(); -} - -int sixenseGetHistorySize() { - return FORWARD(); -} - -int sixenseGetData(int which, int index_back, sixenseControllerData* data) { - return FORWARD(which, index_back, data); -} -int sixenseGetAllData(int index_back, sixenseAllControllerData* data) { - return FORWARD(index_back, data); -} -int sixenseGetNewestData(int which, sixenseControllerData* data) { - return FORWARD(which, data); -} -int sixenseGetAllNewestData(sixenseAllControllerData* data) { - return FORWARD(data); -} - -int sixenseSetHemisphereTrackingMode(int which_controller, int state) { - return FORWARD(which_controller, state); -} -int sixenseGetHemisphereTrackingMode(int which_controller, int* state) { - return FORWARD(which_controller, state); -} -int sixenseAutoEnableHemisphereTracking(int which_controller) { - return FORWARD(which_controller); -} - -int sixenseSetHighPriorityBindingEnabled(int on_or_off) { - return FORWARD(on_or_off); -} -int sixenseGetHighPriorityBindingEnabled(int* on_or_off) { - return FORWARD(on_or_off); -} - -int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) { - return FORWARD(controller_id, duration_100ms, pattern_id); -} - -int sixenseSetFilterEnabled(int on_or_off) { - return FORWARD(on_or_off); -} -int sixenseGetFilterEnabled(int* on_or_off) { - return FORWARD(on_or_off); -} - -int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) { - return FORWARD(near_range, near_val, far_range, far_val); -} -int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) { - return FORWARD(near_range, near_val, far_range, far_val); -} - -int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) { - return FORWARD(red, green, blue); -} -int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) { - return FORWARD(red, green, blue); -} -#endif From 645ae3c21e96fe299ceac3c2499b218b9fc87213 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 22 Jun 2016 17:02:02 -0700 Subject: [PATCH 13/36] remove tabs --- cmake/macros/TargetSixense.cmake | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmake/macros/TargetSixense.cmake b/cmake/macros/TargetSixense.cmake index 28128d8b79..07dcfe67e4 100644 --- a/cmake/macros/TargetSixense.cmake +++ b/cmake/macros/TargetSixense.cmake @@ -6,11 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_SIXENSE) - if(NOT APPLE) - add_dependency_external_projects(sixense) - find_package(Sixense REQUIRED) - target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) - add_definitions(-DHAVE_SIXENSE) - endif() + if(NOT APPLE) + add_dependency_external_projects(sixense) + find_package(Sixense REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) + add_definitions(-DHAVE_SIXENSE) + endif() endmacro() From c0be9aec6aedd683e12d7d61dfbf9ff210526d70 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 9 Jun 2016 13:28:25 -0700 Subject: [PATCH 14/36] cleanup the callers of the resamplers for mic to net and net to speaker, remove loopback resampler --- libraries/audio-client/src/AudioClient.cpp | 88 +++++++--------------- libraries/audio-client/src/AudioClient.h | 1 - 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 0c7a79e2a3..ac055bb0f6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -102,7 +102,6 @@ AudioClient::AudioClient() : _reverbOptions(&_scriptReverbOptions), _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), - _loopbackResampler(NULL), _outgoingAvatarAudioSequenceNumber(0), _audioOutputIODevice(_receivedAudioStream, this), _stats(&_receivedAudioStream), @@ -315,54 +314,35 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, // FIXME: directly using 24khz has a bug somewhere that causes channels to be swapped. // Continue using our internal resampler, for now. - if (true || !audioDevice.isFormatSupported(desiredAudioFormat)) { - qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - qCDebug(audioclient, "The desired audio format is not supported by this device"); + qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; - if (desiredAudioFormat.channelCount() == 1) { - adjustedAudioFormat = desiredAudioFormat; - adjustedAudioFormat.setChannelCount(2); - - if (false && audioDevice.isFormatSupported(adjustedAudioFormat)) { - return true; - } else { - adjustedAudioFormat.setChannelCount(1); - } - } - - const int FORTY_FOUR = 44100; - - adjustedAudioFormat = desiredAudioFormat; + const int FORTY_FOUR = 44100; + adjustedAudioFormat = desiredAudioFormat; #ifdef Q_OS_ANDROID - adjustedAudioFormat.setSampleRate(FORTY_FOUR); + adjustedAudioFormat.setSampleRate(FORTY_FOUR); #else - const int HALF_FORTY_FOUR = FORTY_FOUR / 2; + const int HALF_FORTY_FOUR = FORTY_FOUR / 2; - if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { - // use 48, which is a sample downsample, upsample - adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); - } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { - // use 22050, resample but closer to 24 - adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); - } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { - // use 48000, resample - adjustedAudioFormat.setSampleRate(FORTY_FOUR); - } + if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { + // use 48, which is a sample downsample, upsample + adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); + } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { + // use 22050, resample but closer to 24 + adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); + } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { + // use 48000, resample + adjustedAudioFormat.setSampleRate(FORTY_FOUR); + } #endif - if (adjustedAudioFormat != desiredAudioFormat) { - // return the nearest in case it needs 2 channels - adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); - return true; - } else { - return false; - } - } else { - // set the adjustedAudioFormat to the desiredAudioFormat, since it will work - adjustedAudioFormat = desiredAudioFormat; + if (adjustedAudioFormat != desiredAudioFormat) { + // return the nearest in case it needs 2 channels + adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); return true; + } else { + return false; } } @@ -467,11 +447,6 @@ void AudioClient::stop() { // "switch" to invalid devices in order to shut down the state switchInputToAudioDevice(QAudioDeviceInfo()); switchOutputToAudioDevice(QAudioDeviceInfo()); - - if (_loopbackResampler) { - delete _loopbackResampler; - _loopbackResampler = NULL; - } } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { @@ -675,16 +650,10 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } } - // do we need to setup a resampler? - if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) { - qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback."; - - assert(_inputFormat.sampleSize() == 16); - assert(_outputFormat.sampleSize() == 16); - int channelCount = (_inputFormat.channelCount() == 2 && _outputFormat.channelCount() == 2) ? 2 : 1; - - _loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount); - } + // NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern + // multimedia OS they should be. If there is a device that this is not true for, we can + // add back support to do resampling. + Q_ASSERT(_inputFormat.sampleRate() == _outputFormat.sampleRate()); static QByteArray loopBackByteArray; @@ -696,7 +665,8 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - possibleResampling(_loopbackResampler, + auto NO_RESAMPLER = nullptr; + possibleResampling(NO_RESAMPLER, inputSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); @@ -1039,12 +1009,6 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice _networkToOutputResampler = NULL; } - if (_loopbackResampler) { - // if we were using an input to output resample, delete it here - delete _loopbackResampler; - _loopbackResampler = NULL; - } - if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3a14c878f6..dc46db5657 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -260,7 +260,6 @@ private: // possible streams needed for resample AudioSRC* _inputToNetworkResampler; AudioSRC* _networkToOutputResampler; - AudioSRC* _loopbackResampler; // Adds Reverb void configureReverb(); From 6111e4952afdfced12aca6ac0eb1c2d1906793d8 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 23 Jun 2016 10:59:26 -0700 Subject: [PATCH 15/36] fix comments --- libraries/audio-client/src/AudioClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index ac055bb0f6..7628c09748 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -326,13 +326,13 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, const int HALF_FORTY_FOUR = FORTY_FOUR / 2; if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { - // use 48, which is a sample downsample, upsample + // use 48, which is a simple downsample, upsample adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); } else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) { // use 22050, resample but closer to 24 adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR); } else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) { - // use 48000, resample + // use 44100, resample adjustedAudioFormat.setSampleRate(FORTY_FOUR); } #endif From 2fd38c1585f0f1d3b2a3da4df98777c5ca491050 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 23 Jun 2016 13:10:15 -0700 Subject: [PATCH 16/36] when adding or editing an entity from a script, make the _lastEdited in the transmitted properties be the same as the one in the Entity in the local tree --- libraries/entities/src/EntityScriptingInterface.cpp | 2 ++ libraries/entities/src/EntityTree.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e0863041a1..55d93d5b5b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -179,6 +179,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties } entity->setLastBroadcast(usecTimestampNow()); + propertiesWithSimID.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -376,6 +377,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& properties.setQueryAACube(entity->getQueryAACube()); } entity->setLastBroadcast(usecTimestampNow()); + properties.setLastEdited(entity->getLastEdited()); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ad0f929e59..7ebfecbe8e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -898,7 +898,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c entityItemID, properties); endDecode = usecTimestampNow(); - const quint64 LAST_EDITED_SERVERSIDE_BUMP = 10000; // usec + const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { // this node is only allowed to rez temporary entities. if need be, cap the lifetime. if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || From fc42a3aef52759e8f8cda7a665deeede5377345e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 23 Jun 2016 15:36:47 -0700 Subject: [PATCH 17/36] Grab script hotspot work * Updated grab/equip logic to use sphere vs sphere tests, instead of sphere vs entity bounding box. * Added debug flag for visualizing grab spheres. * hotspot overlays are now updated as the objects they are attached to move. * You can now use the search beam to near grab large objects, as well as the sphere sphere test. * Optimized EntityPropertyCache to make a single call to Entities.getEntityProperties instead of three. * Moved grab script options from the "Developer > Hands" menu to the "Developer > Grab Script" menu. --- .../system/controllers/handControllerGrab.js | 312 +++++++++++------- 1 file changed, 200 insertions(+), 112 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 31f8ebd73f..1772e55015 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,10 +10,9 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); -Script.include("../libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -38,6 +37,9 @@ var THUMB_ON_VALUE = 0.5; 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 DRAW_GRAB_SPHERES = false; +var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; // @@ -68,16 +70,20 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray + // // near grabbing // -var GRAB_RADIUS = 0.06; // if the ray misses but an object is this close, it will still be selected +var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. + var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected + +var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. + var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed -var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds // @@ -254,7 +260,8 @@ function restore2DMode() { } } -// constructor +// EntityPropertiesCache is a helper class that contains a cache of entity properties. +// the hope is to prevent excess calls to Entity.getEntityProperties() function EntityPropertiesCache() { this.cache = {}; } @@ -265,34 +272,55 @@ EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); var _this = this; entities.forEach(function (x) { - _this.addEntity(x); + _this.updateEntity(x); }); }; -EntityPropertiesCache.prototype.addEntity = function(entityID) { +EntityPropertiesCache.prototype.updateEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); - var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - var wearableProps = getEntityCustomData("wearable", entityID, {}); - this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps }; + + // convert props.userData from a string to an object. + var userData = {}; + if (props.userData) { + try { + userData = JSON.parse(props.userData); + } catch(err) { + print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err); + } + } + props.userData = userData; + + this.cache[entityID] = props; }; EntityPropertiesCache.prototype.getEntities = function() { return Object.keys(this.cache); } EntityPropertiesCache.prototype.getProps = function(entityID) { var obj = this.cache[entityID] - return obj ? obj.props : undefined; + return obj ? obj : undefined; }; EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.grabbableProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; + } else { + return undefined; + } }; EntityPropertiesCache.prototype.getGrabProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.grabProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.grabKey ? props.userData.grabKey : {}; + } else { + return undefined; + } }; EntityPropertiesCache.prototype.getWearableProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.wearableProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.wearable ? props.userData.wearable : {}; + } else { + return undefined; + } }; function MyController(hand) { @@ -323,7 +351,6 @@ function MyController(hand) { //for visualizations this.overlayLine = null; this.particleBeamObject = null; - this.grabSphere = null; //for lights this.spotlight = null; @@ -384,7 +411,7 @@ function MyController(hand) { } this.setState = function(newState, reason) { - this.grabSphereOff(); + if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); @@ -491,39 +518,6 @@ function MyController(hand) { } } - this.grabSphereOn = function() { - var color = {red: 0, green: 255, blue: 0}; - if (this.grabSphere === null) { - var sphereProperties = { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - ignoreRayIntersection: true, - drawInFront: true, // Even when burried inside of something, show it. - visible: true - } - this.grabSphere = Overlays.addOverlay("sphere", sphereProperties); - } else { - Overlays.editOverlay(this.grabSphere, { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - visible: true - }); - } - } - - this.grabSphereOff = function() { - if (this.grabSphere !== null) { - Overlays.deleteOverlay(this.grabSphere); - this.grabSphere = null; - } - }; - this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { @@ -873,51 +867,123 @@ function MyController(hand) { } }; - this.searchEnter = function() { - this.equipHotspotOverlays = []; + this.createHotspots = function () { + + var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var HAND_SPHERE_ALPHA = 0.7; + var HAND_SPHERE_RADIUS = 0.01; + + var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var EQUIP_SPHERE_ALPHA = 0.3; + + var NEAR_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; + var NEAR_SPHERE_ALPHA = 0.1; + + this.hotspotOverlays = []; + + if (DRAW_HAND_SPHERES) { + // add tiny spheres around the palm. + var handPosition = this.getHandPosition(); + var overlay = Overlays.addOverlay("sphere", { + position: handPosition, + size: HAND_SPHERE_RADIUS * 2, + color: HAND_SPHERE_COLOR, + alpha: HAND_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + this.hotspotOverlays.push({ + entityID: undefined, + overlay: overlay, + type: "hand" + }); + } // find entities near the avatar that might be equipable. - var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var i, l = entities.length; - for (i = 0; i < l; i++) { - var props = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); - // does this entity have an attach point? - var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData && wearableData.joints) { - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData.joints[handJointName]) { - var handOffsetPos = wearableData.joints[handJointName][0]; - var handOffsetRot = wearableData.joints[handJointName][1]; + this.entityPropertyCache.clear(); + this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var handOffsetXform = new Xform(handOffsetRot, handOffsetPos); - var objectXform = new Xform(props.rotation, props.position); - var overlayXform = Xform.mul(objectXform, handOffsetXform.inv()); + var _this = this; + this.entityPropertyCache.getEntities().forEach(function (entityID) { + if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) { + var props = _this.entityPropertyCache.getProps(entityID); - // draw the hotspot - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - rotation: overlayXform.rot, - position: overlayXform.pos, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - })); - } + overlay = Overlays.addOverlay("sphere", { + rotation: props.rotation, + position: props.position, + size: EQUIP_RADIUS * 2, + color: EQUIP_SPHERE_COLOR, + alpha: EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + _this.hotspotOverlays.push({ + entityID: entityID, + overlay: overlay, + type: "equip" + }); } - } + + if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { + var props = _this.entityPropertyCache.getProps(entityID); + + overlay = Overlays.addOverlay("sphere", { + rotation: props.rotation, + position: props.position, + size: NEAR_GRAB_RADIUS * 2, + color: NEAR_SPHERE_COLOR, + alpha: NEAR_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + _this.hotspotOverlays.push({ + entityID: entityID, + overlay: overlay, + type: "near" + }); + } + }); + }; + + this.updateHotspots = function() { + var _this = this; + this.hotspotOverlays.forEach(function (overlayInfo) { + if (overlayInfo.type === "hand") { + Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() }); + } else if (overlayInfo.type === "equip") { + _this.entityPropertyCache.updateEntity(overlayInfo.entityID); + var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + } else if (overlayInfo.type === "near") { + _this.entityPropertyCache.updateEntity(overlayInfo.entityID); + var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + } + }); + }; + + this.destroyHotspots = function() { + this.hotspotOverlays.forEach(function (overlayInfo) { + Overlays.deleteOverlay(overlayInfo.overlay); + }); + this.hotspotOverlays = []; + }; + + this.searchEnter = function() { + this.createHotspots(); }; this.searchExit = function() { - - // delete all equip hotspots - var i, l = this.equipHotspotOverlays.length; - for (i = 0; i < l; i++) { - Overlays.deleteOverlay(this.equipHotspotOverlays[i]); - } - this.equipHotspotOverlays = []; + this.destroyHotspots(); }; /// @@ -984,7 +1050,13 @@ function MyController(hand) { return grabbableProps && grabbableProps.wantsTrigger; }; - this.entityIsEquippable = function (entityID, handPosition) { + this.entityIsEquippableWithoutDistanceCheck = function (entityID) { + var props = this.entityPropertyCache.getProps(entityID); + var handPosition = props.position; + return this.entityIsEquippableWithDistanceCheck(entityID, handPosition); + }; + + this.entityIsEquippableWithDistanceCheck = function (entityID, handPosition) { var props = this.entityPropertyCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var grabProps = this.entityPropertyCache.getGrabProps(entityID); @@ -998,9 +1070,9 @@ function MyController(hand) { return false; } - if (distance > NEAR_PICK_MAX_DISTANCE) { + if (distance > EQUIP_RADIUS) { if (debug) { - print("equip is skipping '" + props.name + "': too far away."); + print("equip is skipping '" + props.name + "': too far away, " + distance + " meters"); } return false; } @@ -1113,7 +1185,7 @@ function MyController(hand) { return true; }; - this.entityIsNearGrabbable = function(entityID, handPosition) { + this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { if (!this.entityIsGrabbable(entityID)) { return false; @@ -1123,7 +1195,7 @@ function MyController(hand) { var distance = Vec3.distance(props.position, handPosition); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - if (distance > NEAR_PICK_MAX_DISTANCE) { + if (distance > maxDistance) { // too far away, don't grab if (debug) { print(" grab is skipping '" + props.name + "': too far away."); @@ -1138,6 +1210,8 @@ function MyController(hand) { var _this = this; var name; + this.updateHotspots(); + this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; @@ -1151,16 +1225,12 @@ function MyController(hand) { var handPosition = this.getHandPosition(); - if (SHOW_GRAB_SPHERE) { - this.grabSphereOn(); - } - this.entityPropertyCache.clear(); - this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); + this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS); var candidateEntities = this.entityPropertyCache.getEntities(); var equippableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsEquippable(entity, handPosition); + return _this.entityIsEquippableWithDistanceCheck(entity, handPosition); }); var entity; @@ -1181,16 +1251,19 @@ function MyController(hand) { } } + var grabbableEntities = candidateEntities.filter(function (entity) { + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_RADIUS); + }); + var rayPickInfo = this.calcRayPickInfo(this.hand); this.intersectionDistance = rayPickInfo.distance; - if (rayPickInfo.entityID) { - candidateEntities.push(rayPickInfo.entityID); - this.entityPropertyCache.addEntity(rayPickInfo.entityID); - } - var grabbableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsNearGrabbable(entity, handPosition); - }); + if (rayPickInfo.entityID) { + this.entityPropertyCache.updateEntity(rayPickInfo.entityID); + if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { + grabbableEntities.push(rayPickInfo.entityID); + } + } if (grabbableEntities.length > 0) { // sort by distance @@ -1732,11 +1805,11 @@ function MyController(hand) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_PICK_RADIUS * 2.0) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, EQUIP_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + @@ -2182,17 +2255,32 @@ function cleanup() { Script.scriptEnding.connect(cleanup); Script.update.connect(update); +if (!Menu.menuExists("Developer > Grab Script")) { + Menu.addMenu("Developer > Grab Script"); +} + Menu.addMenuItem({ - menuName: "Developer > Hands", + menuName: "Developer > Grab Script", menuItemName: "Drop Without Shake", isCheckable: true, isChecked: DROP_WITHOUT_SHAKE }); +Menu.addMenuItem({ + menuName: "Developer > Grab Script", + menuItemName: "Draw Grab Spheres", + isCheckable: true, + isChecked: DRAW_GRAB_SPHERES +}); + function handleMenuItemEvent(menuItem) { if (menuItem === "Drop Without Shake") { DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); } + if (menuItem === "Draw Grab Spheres") { + DRAW_GRAB_SPHERES = Menu.isOptionChecked("Draw Grab Spheres"); + DRAW_HAND_SPHERES = DRAW_GRAB_SPHERES; + } } Menu.menuItemEvent.connect(handleMenuItemEvent); From f8391f00629535b9849d928b0075c0f032dc61f2 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 23 Jun 2016 16:51:15 -0700 Subject: [PATCH 18/36] fix reticle bugs --- interface/src/Application.cpp | 26 +++++++++---------- interface/src/Application.h | 1 + libraries/ui/src/OffscreenUi.cpp | 5 ++-- .../controllers/handControllerPointer.js | 21 +++++++++++---- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 37c3b361bf..26da43fb58 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -921,17 +921,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : cycleCamera(); } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { if (!offscreenUi->navigationFocused()) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); + toggleMenuUnderReticle(); } } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); - } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { - if (!offscreenUi->navigationFocused()) { - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); - } + toggleMenuUnderReticle(); } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y }); @@ -1240,7 +1233,16 @@ QString Application::getUserAgent() { return userAgent; } - +void Application::toggleMenuUnderReticle() const { + // In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly + // different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both + // on the menu and off. + // Even in 2D, it is arguable whether the user would want the menu to be to the side. + const float X_LEFT_SHIFT = 50.0; + auto offscreenUi = DependencyManager::get(); + auto reticlePosition = getApplicationCompositor().getReticlePosition(); + offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y)); +} void Application::checkChangeCursor() { QMutexLocker locker(&_changeCursorLock); @@ -2462,9 +2464,7 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) { - auto offscreenUi = DependencyManager::get(); - auto reticlePosition = getApplicationCompositor().getReticlePosition(); - offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y)); + toggleMenuUnderReticle(); } _keysPressed.remove(event->key()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 5beaa5b455..6857ba2a3a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -408,6 +408,7 @@ private: static void dragEnterEvent(QDragEnterEvent* event); void maybeToggleMenuVisible(QMouseEvent* event) const; + void toggleMenuUnderReticle() const; MainWindow* _window; QElapsedTimer& _sessionRunTimer; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index fa1a31d196..1a7d4b2328 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -492,10 +492,9 @@ void OffscreenUi::unfocusWindows() { Q_ASSERT(invokeResult); } -void OffscreenUi::toggleMenu(const QPoint& screenPosition) { +void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works - auto virtualPos = mapToVirtualScreen(screenPosition, nullptr); - QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos)); + QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition)); } diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 374be0d1a1..a0f1f47b3c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -208,9 +208,9 @@ function isShakingMouse() { // True if the person is waving the mouse around try return isShaking; } var NON_LINEAR_DIVISOR = 2; -var MINIMUM_SEEK_DISTANCE = 0.01; -function updateSeeking() { - if (!Reticle.visible || isShakingMouse()) { +var MINIMUM_SEEK_DISTANCE = 0.1; +function updateSeeking(doNotStartSeeking) { + if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) { if (!isSeeking) { print('Start seeking mouse.'); isSeeking = true; @@ -224,8 +224,8 @@ function updateSeeking() { if (!lookAt2D) { // If this happens, something has gone terribly wrong. print('Cannot seek without lookAt position'); isSeeking = false; - return; - } // E.g., if parallel to location in HUD + return; // E.g., if parallel to location in HUD + } var copy = Reticle.position; function updateDimension(axis) { var distanceBetween = lookAt2D[axis] - Reticle.position[axis]; @@ -353,6 +353,16 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () { + // Allow the reticle depth to be set correctly: + // Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move + // so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove. + // We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove + // after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse. + Script.setTimeout(function () { + Reticle.setPosition(Reticle.position); + }, 0); +}); // Partial smoothed trigger is activation. clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand)); clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); @@ -386,6 +396,7 @@ function update() { expireMouseCursor(); clearSystemLaser(); } + updateSeeking(true); if (!handControllerLockOut.expired(now)) { return off(); // Let them use mouse it in peace. } From 7bd553c09cea7784828354085901adf788f37a0c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 23 Jun 2016 17:03:15 -0700 Subject: [PATCH 19/36] near grab logic to uses sphere vs entity box instead of sphere vs sphere. Adjusted debug drawing accordingly. --- .../system/controllers/handControllerGrab.js | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 26f977f9b0..b1cf3927d9 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -38,7 +38,7 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only var PICK_WITH_HAND_RAY = true; -var DRAW_GRAB_SPHERES = false; +var DRAW_GRAB_BOXES = false; var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; @@ -80,6 +80,8 @@ var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand + var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object @@ -870,26 +872,48 @@ function MyController(hand) { this.createHotspots = function () { var props, overlay; - var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; - var HAND_SPHERE_ALPHA = 0.7; - var HAND_SPHERE_RADIUS = 0.01; + var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var HAND_EQUIP_SPHERE_ALPHA = 0.7; + var HAND_EQUIP_SPHERE_RADIUS = 0.01; + + var HAND_GRAB_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; + var HAND_GRAB_SPHERE_ALPHA = 0.3; + var HAND_GRAB_SPHERE_RADIUS = NEAR_GRAB_RADIUS; var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; var EQUIP_SPHERE_ALPHA = 0.3; - var NEAR_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; - var NEAR_SPHERE_ALPHA = 0.1; + var GRAB_BOX_COLOR = { red: 90, green: 90, blue: 255 }; + var GRAB_BOX_ALPHA = 0.1; this.hotspotOverlays = []; if (DRAW_HAND_SPHERES) { - // add tiny spheres around the palm. + // add tiny green sphere around the palm. var handPosition = this.getHandPosition(); overlay = Overlays.addOverlay("sphere", { position: handPosition, - size: HAND_SPHERE_RADIUS * 2, - color: HAND_SPHERE_COLOR, - alpha: HAND_SPHERE_ALPHA, + size: HAND_EQUIP_SPHERE_RADIUS * 2, + color: HAND_EQUIP_SPHERE_COLOR, + alpha: HAND_EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + this.hotspotOverlays.push({ + entityID: undefined, + overlay: overlay, + type: "hand" + }); + + // add larger blue sphere around the palm. + overlay = Overlays.addOverlay("sphere", { + position: handPosition, + size: HAND_GRAB_SPHERE_RADIUS * 2, + color: HAND_GRAB_SPHERE_COLOR, + alpha: HAND_GRAB_SPHERE_ALPHA, solid: true, visible: true, ignoreRayIntersection: true, @@ -931,15 +955,15 @@ function MyController(hand) { }); } - if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { + if (DRAW_GRAB_BOXES && _this.entityIsGrabbable(entityID)) { props = _this.entityPropertyCache.getProps(entityID); - overlay = Overlays.addOverlay("sphere", { + overlay = Overlays.addOverlay("cube", { rotation: props.rotation, position: props.position, - size: NEAR_GRAB_RADIUS * 2, - color: NEAR_SPHERE_COLOR, - alpha: NEAR_SPHERE_ALPHA, + size: props.dimensions, //{x: props.dimensions.x, y: props.dimensions.y, z: props.dimensions.z}, + color: GRAB_BOX_COLOR, + alpha: GRAB_BOX_ALPHA, solid: true, visible: true, ignoreRayIntersection: true, @@ -1254,7 +1278,7 @@ function MyController(hand) { } var grabbableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_RADIUS); + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1807,11 +1831,11 @@ function MyController(hand) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_GRAB_PICK_RADIUS * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - var nearPickedCandidateEntities = Entities.findEntities(handPosition, EQUIP_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + @@ -2270,18 +2294,18 @@ Menu.addMenuItem({ Menu.addMenuItem({ menuName: "Developer > Grab Script", - menuItemName: "Draw Grab Spheres", + menuItemName: "Draw Grab Boxes", isCheckable: true, - isChecked: DRAW_GRAB_SPHERES + isChecked: DRAW_GRAB_BOXES }); function handleMenuItemEvent(menuItem) { if (menuItem === "Drop Without Shake") { DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); } - if (menuItem === "Draw Grab Spheres") { - DRAW_GRAB_SPHERES = Menu.isOptionChecked("Draw Grab Spheres"); - DRAW_HAND_SPHERES = DRAW_GRAB_SPHERES; + if (menuItem === "Draw Grab Boxes") { + DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes"); + DRAW_HAND_SPHERES = DRAW_GRAB_BOXES; } } From 1de1c632afbb4f6f124108d32265e2aa3ec25c53 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 17:16:42 -0700 Subject: [PATCH 20/36] use private_description instead of description for domain settings --- domain-server/resources/web/settings/js/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index c2cb2ecb80..4188e86548 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -555,7 +555,7 @@ function createNewDomainID(description, justConnected) { // get the JSON object ready that we'll use to create a new domain var domainJSON = { "domain": { - "description": description + "private_description": description }, "access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val() } @@ -748,8 +748,8 @@ function chooseFromHighFidelityDomains(clickedButton) { _.each(data.data.domains, function(domain){ var domainString = ""; - if (domain.description) { - domainString += '"' + domain.description + '" - '; + if (domain.private_description) { + domainString += '"' + domain.private_description + '" - '; } domainString += domain.id; From 13310923c4440ec6b0a97dbf89627dc63fe832ad Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 19:38:23 -0700 Subject: [PATCH 21/36] reset to temp domain after logout --- .../resources/web/settings/js/settings.js | 2 ++ domain-server/src/DomainServer.cpp | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 4188e86548..4f153d6190 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -457,6 +457,8 @@ function disonnectHighFidelityAccount() { }, function(){ // we need to post to settings to clear the access-token $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); + // reset the domain id to get a new temporary name + $(Settings.DOMAIN_ID_SELECTOR).val('').change(); saveSettings(); }); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 192c0d26e6..ca0d7728dd 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -111,12 +111,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : return; } - // check for the temporary name parameter - const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; - if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { - getTemporaryName(); - } - setupNodeListAndAssignments(); setupAutomaticNetworking(); if (!getID().isNull()) { @@ -125,6 +119,12 @@ DomainServer::DomainServer(int argc, char* argv[]) : sendHeartbeatToMetaverse(); } + // check for the temporary name parameter + const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; + if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { + getTemporaryName(); + } + _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request _metadata = new DomainMetadata(this); @@ -251,12 +251,13 @@ void DomainServer::getTemporaryName(bool force) { // check if we already have a domain ID const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); + qInfo() << "Requesting temporary domain name"; if (idValueVariant) { - qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."; + qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); if (force) { - qWarning() << "Temporary domain name will be requested to replace it."; + qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); } else { - qWarning() << "Temporary domain name will not be requested."; + qInfo() << "Abandoning request of temporary domain name."; return; } } From b1b378a91feb062e160cf794002e59c13e5ed11b Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 19:38:41 -0700 Subject: [PATCH 22/36] add back access token to domain-server --- domain-server/src/DomainServer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ca0d7728dd..6e5c1fd361 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -470,6 +470,9 @@ bool DomainServer::resetAccountManagerAccessToken() { void DomainServer::setupAutomaticNetworking() { qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; + + resetAccountManagerAccessToken(); + _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); From 1fcd7aa0c44d714fa4505b9b97fdad698ed9a674 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 23 Jun 2016 19:54:34 -0700 Subject: [PATCH 23/36] add build version to heartbeat --- domain-server/src/DomainServer.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 223cab61da..d421c6554b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1081,6 +1081,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { // Setup the domain object to send to the data server QJsonObject domainObject; + // add the version + static const QString VERSION_KEY = "version"; + domainObject[VERSION_KEY] = BuildInfo::VERSION; + + // add networking if (!networkAddress.isEmpty()) { static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress; @@ -1089,10 +1094,10 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking"; domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; - // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings - const QString RESTRICTED_ACCESS_FLAG = "restricted"; - // consider the domain to have restricted access if "anonymous" connections can't connect to the domain. + // add access level for anonymous connections + // consider the domain to be "restricted" if anonymous connections are disallowed + static const QString RESTRICTED_ACCESS_FLAG = "restricted"; NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous); domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain; From 88ad570024a927d80b24fbc036897fa7bb0b971f Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 10:16:26 -0700 Subject: [PATCH 24/36] Revert "Uses spaces, not tabs" This reverts commit 7e5b9db13a688100d9d587c1a4147e6a65f15e78. --- interface/src/Application.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 53b9c18360..ddfa5dae8b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5349,14 +5349,14 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - - // go into first person when they are in HMD mode, since 3rd person HMD is dumb - if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { - menu->setIsOptionChecked(MenuOption::FirstPerson, true); - cameraMenuChanged(); - } - - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + + // go into first person when they are in HMD mode, since 3rd person HMD is dumb + if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From 41d38ab63045405b7379a1516ebd7056460e97bf Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 10:16:40 -0700 Subject: [PATCH 25/36] Revert "Automatically enter first person when in HMD mode" This reverts commit 3c9f3f392736b56a0641a6f6707c00077b2871bf. --- interface/src/Application.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ddfa5dae8b..d20a62fc7b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5350,13 +5350,7 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); - // go into first person when they are in HMD mode, since 3rd person HMD is dumb - if (isHMDMode() && !menu->isOptionChecked(MenuOption::FirstPerson)) { - menu->setIsOptionChecked(MenuOption::FirstPerson, true); - cameraMenuChanged(); - } - - Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } mat4 Application::getEyeProjection(int eye) const { From 39ff03cfb51acf0bd719fd5c39d1ff8caaf2a357 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 24 Jun 2016 10:53:46 -0700 Subject: [PATCH 26/36] reduce log spam in Vive plugins --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 4 +++- plugins/openvr/src/OpenVrHelpers.cpp | 28 ++++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 5233ad644a..cfb374e3bd 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -96,7 +96,9 @@ bool OpenVrDisplayPlugin::internalActivate() { glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET); _sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos)); } else { - qDebug() << "OpenVR: error could not get chaperone pointer"; + #if DEV_BUILD + qDebug() << "OpenVR: error could not get chaperone pointer"; + #endif } return Parent::internalActivate(); diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index e71c8942d6..399712d920 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -64,17 +64,25 @@ vr::IVRSystem* acquireOpenVrSystem() { if (hmdPresent) { Lock lock(mutex); if (!activeHmd) { - qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building"; + #endif vr::EVRInitError eError = vr::VRInitError_None; activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); - qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; + #endif } if (activeHmd) { - qCDebug(displayplugins) << "OpenVR: incrementing refcount"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: incrementing refcount"; + #endif ++refCount; } } else { - qCDebug(displayplugins) << "OpenVR: no hmd present"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: no hmd present"; + #endif } return activeHmd; } @@ -82,10 +90,14 @@ vr::IVRSystem* acquireOpenVrSystem() { void releaseOpenVrSystem() { if (activeHmd) { Lock lock(mutex); - qCDebug(displayplugins) << "OpenVR: decrementing refcount"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: decrementing refcount"; + #endif --refCount; if (0 == refCount) { - qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system"; + #if DEV_BUILD + qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system"; + #endif vr::VR_Shutdown(); activeHmd = nullptr; } @@ -261,7 +273,9 @@ void handleOpenVrEvents() { default: break; } - qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; + #if DEV_BUILD + qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; + #endif } } From a095da31fd8c1422058da1de0622554124f4f56c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 24 Jun 2016 11:48:55 -0700 Subject: [PATCH 27/36] Fix initial visibility of QML windows from scripts --- libraries/ui/src/QmlWindowClass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index b8834f0549..c0eba4abf3 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -118,7 +118,7 @@ void QmlWindowClass::initQml(QVariantMap properties) { } bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool(); - object->setProperty(VISIBILE_PROPERTY, visible); + object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); object->setProperty(SOURCE_PROPERTY, _source); // Forward messages received from QML on to the script From d45694836fe3ffbfe21b8cc0b2642bbadd05e72c Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 11:57:12 -0700 Subject: [PATCH 28/36] Add signal for display mode change in HMD --- interface/src/scripting/HMDScriptingInterface.cpp | 3 +++ .../src/display-plugins/AbstractHMDScriptingInterface.h | 1 + 2 files changed, 4 insertions(+) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 7bf1547a3c..9b633ef96d 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -19,6 +19,9 @@ #include "Application.h" HMDScriptingInterface::HMDScriptingInterface() { + connect(qApp, &Application::activeDisplayPluginChanged, [this]{ + emit displayModeChanged(isHMDMode()); + }); } glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const { diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index 5df58ce677..f260fa959f 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -31,6 +31,7 @@ public: signals: void IPDScaleChanged(); + void displayModeChanged(bool isHMDMode); private: float _IPDScale{ 1.0 }; From 0c56af7ae8042982eb1f0969261c7908e513d5f7 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 11:57:50 -0700 Subject: [PATCH 29/36] Add default script to enter first person for HMD --- scripts/defaultScripts.js | 1 + scripts/system/firstPersonHMD.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 scripts/system/firstPersonHMD.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..f6d9d95ada 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -21,3 +21,4 @@ Script.load("system/controllers/handControllerPointer.js"); Script.load("system/controllers/squeezeHands.js"); Script.load("system/controllers/grab.js"); Script.load("system/dialTone.js"); +Script.load("system/firstPersonHMD.js"); diff --git a/scripts/system/firstPersonHMD.js b/scripts/system/firstPersonHMD.js new file mode 100644 index 0000000000..082c6304be --- /dev/null +++ b/scripts/system/firstPersonHMD.js @@ -0,0 +1,17 @@ +// +// firstPersonHMD.js +// system +// +// Created by Zander Otavka on 6/24/16 +// Copyright 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 +// + +// Automatically enter first person mode when entering HMD mode +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + Camera.setModeString("first person"); + } +}); From 8bf72f28da84d177e67e920fa1e3f1b243a587b6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 24 Jun 2016 12:06:02 -0700 Subject: [PATCH 30/36] Fix for grab script search ray length --- scripts/system/controllers/handControllerGrab.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 7706132c58..93d2269584 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1173,10 +1173,12 @@ function MyController(hand) { } var rayPickInfo = this.calcRayPickInfo(this.hand); - this.intersectionDistance = rayPickInfo.distance; if (rayPickInfo.entityID) { candidateEntities.push(rayPickInfo.entityID); this.entityPropertyCache.addEntity(rayPickInfo.entityID); + this.intersectionDistance = rayPickInfo.distance; + } else { + this.intersectionDistance = 0; } var grabbableEntities = candidateEntities.filter(function (entity) { From dd023e16fd53c500b6741fd6b7ff60ff05a61501 Mon Sep 17 00:00:00 2001 From: Zander Otavka Date: Fri, 24 Jun 2016 13:16:24 -0700 Subject: [PATCH 31/36] Tabs -> spaces --- interface/src/scripting/HMDScriptingInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 9b633ef96d..49c6635fb3 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -19,9 +19,9 @@ #include "Application.h" HMDScriptingInterface::HMDScriptingInterface() { - connect(qApp, &Application::activeDisplayPluginChanged, [this]{ - emit displayModeChanged(isHMDMode()); - }); + connect(qApp, &Application::activeDisplayPluginChanged, [this]{ + emit displayModeChanged(isHMDMode()); + }); } glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const { From 7da854d98c70cf4272c4d68c84f898789e6f28e3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 24 Jun 2016 13:35:47 -0700 Subject: [PATCH 32/36] do not reset hud from button --- interface/src/ui/OverlayConductor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 6a99641ce4..7d0dc6c650 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -118,10 +118,12 @@ void OverlayConductor::update(float dt) { bool isDriving = updateAvatarHasDriveInput(); bool drivingChanged = prevDriving != isDriving; bool isAtRest = updateAvatarIsAtRest(); + bool shouldRecenter = false; if (_flags & SuppressedByDrive) { if (!isDriving) { _flags &= ~SuppressedByDrive; + shouldRecenter = true; } } else { if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { @@ -132,6 +134,7 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByHead) { if (isAtRest) { _flags &= ~SuppressedByHead; + shouldRecenter = true; } } else { if (_hmdMode && headOutsideOverlay()) { @@ -143,8 +146,8 @@ void OverlayConductor::update(float dt) { bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); - if (targetVisible && _hmdMode) { - centerUI(); - } + } + if (shouldRecenter) { + centerUI(); } } From 2ae0a7defc2bfbcdb4fc030d56153e1736363d37 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 24 Jun 2016 14:46:46 -0700 Subject: [PATCH 33/36] If there are TWO conditions holding things back, weight for them both to clear. --- interface/src/ui/OverlayConductor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 7d0dc6c650..e18522cb2f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -123,7 +123,9 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByDrive) { if (!isDriving) { _flags &= ~SuppressedByDrive; - shouldRecenter = true; + if (_flags & SuppressMask) { + shouldRecenter = true; + } } } else { if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { @@ -134,7 +136,9 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByHead) { if (isAtRest) { _flags &= ~SuppressedByHead; - shouldRecenter = true; + if (_flags & SuppressMask) { + shouldRecenter = true; + } } } else { if (_hmdMode && headOutsideOverlay()) { From 9ae3c386166a6834c8c571324189c65d1fe51b69 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 24 Jun 2016 15:26:24 -0700 Subject: [PATCH 34/36] doh! --- interface/src/ui/OverlayConductor.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index e18522cb2f..2ee106b6b3 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -123,9 +123,7 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByDrive) { if (!isDriving) { _flags &= ~SuppressedByDrive; - if (_flags & SuppressMask) { - shouldRecenter = true; - } + shouldRecenter = true; } } else { if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { @@ -136,9 +134,7 @@ void OverlayConductor::update(float dt) { if (_flags & SuppressedByHead) { if (isAtRest) { _flags &= ~SuppressedByHead; - if (_flags & SuppressMask) { - shouldRecenter = true; - } + shouldRecenter = true; } } else { if (_hmdMode && headOutsideOverlay()) { @@ -151,7 +147,7 @@ void OverlayConductor::update(float dt) { if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); } - if (shouldRecenter) { + if (shouldRecenter && !_flags) { centerUI(); } } From 17ce80ed69e97cdb075c27584751cccb78098cf5 Mon Sep 17 00:00:00 2001 From: Marko Kudjerski Date: Fri, 24 Jun 2016 19:28:31 -0700 Subject: [PATCH 35/36] sign executables with SHA256 --- cmake/macros/OptionalWinExecutableSigning.cmake | 2 +- cmake/templates/NSIS.template.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/macros/OptionalWinExecutableSigning.cmake b/cmake/macros/OptionalWinExecutableSigning.cmake index 784aae716f..41ca5762dc 100644 --- a/cmake/macros/OptionalWinExecutableSigning.cmake +++ b/cmake/macros/OptionalWinExecutableSigning.cmake @@ -22,7 +22,7 @@ macro(optional_win_executable_signing) # setup a post build command to sign the executable add_custom_command( TARGET ${TARGET_NAME} POST_BUILD - COMMAND ${SIGNTOOL_EXECUTABLE} sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} + COMMAND ${SIGNTOOL_EXECUTABLE} sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH} ) else () message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 0ea1199c09..4786b12743 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -64,7 +64,7 @@ ; The Inner invocation has written an uninstaller binary for us. ; We need to sign it if it's a production or PR build. !if @PRODUCTION_BUILD@ == 1 - !system '"@SIGNTOOL_EXECUTABLE@" sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 !endif ; Good. Now we can carry on writing the real installer. From bbe9b931bee9364581feb1ab983de329f7d0c301 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 26 Jun 2016 14:59:42 -0700 Subject: [PATCH 36/36] on Linux, don't mess with the menus from the hydra plugin because it makes Qt stop --- plugins/hifiSixense/src/SixenseManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index ade643ec72..baf13f1fae 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -77,6 +77,7 @@ bool SixenseManager::activate() { InputPlugin::activate(); #ifdef HAVE_SIXENSE + #if !defined(Q_OS_LINUX) _container->addMenu(MENU_PATH); _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH, [this] (bool clicked) { setSixenseFilter(clicked); }, @@ -89,6 +90,7 @@ bool SixenseManager::activate() { _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, SHOW_DEBUG_CALIBRATED, [this] (bool clicked) { _inputDevice->setDebugDrawCalibrated(clicked); }, true, false); + #endif auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); @@ -106,8 +108,10 @@ void SixenseManager::deactivate() { InputPlugin::deactivate(); #ifdef HAVE_SIXENSE + #if !defined(Q_OS_LINUX) _container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH); _container->removeMenu(MENU_PATH); + #endif _inputDevice->_poseStateMap.clear();