diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index cbf533bf64..b61fef8525 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -121,9 +121,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : if (_type != NonMetaverse) { // if we have a metaverse domain, we'll use an access token for API calls resetAccountManagerAccessToken(); - } - setupAutomaticNetworking(); + setupAutomaticNetworking(); + } if (!getID().isNull() && _type != NonMetaverse) { // setup periodic heartbeats to metaverse API diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index dd80dadca7..eb9a7c7f6d 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -111,7 +111,7 @@ void DiscoverabilityManager::updateLocation() { } // Update Steam - SteamClient::updateLocation(domainHandler.getHostname(), addressManager->currentFacingAddress()); + SteamClient::updateLocation(domainHandler.getHostname(), addressManager->currentFacingShareableAddress()); } void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) { diff --git a/libraries/fbx/src/FBXReader_Node.cpp b/libraries/fbx/src/FBXReader_Node.cpp index 68e9d6abac..7bacdbc607 100644 --- a/libraries/fbx/src/FBXReader_Node.cpp +++ b/libraries/fbx/src/FBXReader_Node.cpp @@ -140,17 +140,35 @@ QVariant parseBinaryFBXProperty(QDataStream& in, int& position) { } } -FBXNode parseBinaryFBXNode(QDataStream& in, int& position) { - qint32 endOffset; - quint32 propertyCount; - quint32 propertyListLength; +FBXNode parseBinaryFBXNode(QDataStream& in, int& position, bool has64BitPositions = false) { + qint64 endOffset; + quint64 propertyCount; + quint64 propertyListLength; quint8 nameLength; - in >> endOffset; - in >> propertyCount; - in >> propertyListLength; + // FBX 2016 and beyond uses 64bit positions in the node headers, pre-2016 used 32bit values + // our code generally doesn't care about the size that much, so we will use 64bit values + // from here on out, but if the file is an older format we read the stream into temp 32bit + // values and then assign to our actual 64bit values. + if (has64BitPositions) { + in >> endOffset; + in >> propertyCount; + in >> propertyListLength; + position += sizeof(quint64) * 3; + } else { + qint32 tempEndOffset; + quint32 tempPropertyCount; + quint32 tempPropertyListLength; + in >> tempEndOffset; + in >> tempPropertyCount; + in >> tempPropertyListLength; + position += sizeof(quint32) * 3; + endOffset = tempEndOffset; + propertyCount = tempPropertyCount; + propertyListLength = tempPropertyListLength; + } in >> nameLength; - position += sizeof(quint32) * 3 + sizeof(quint8); + position += sizeof(quint8); FBXNode node; const int MIN_VALID_OFFSET = 40; @@ -166,7 +184,7 @@ FBXNode parseBinaryFBXNode(QDataStream& in, int& position) { } while (endOffset > position) { - FBXNode child = parseBinaryFBXNode(in, position); + FBXNode child = parseBinaryFBXNode(in, position, has64BitPositions); if (child.name.isNull()) { return node; @@ -327,15 +345,24 @@ FBXNode FBXReader::parseFBX(QIODevice* device) { // see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation // of the FBX binary format - // skip the rest of the header - const int HEADER_SIZE = 27; - in.skipRawData(HEADER_SIZE); - int position = HEADER_SIZE; + // The first 27 bytes contain the header. + // Bytes 0 - 20: Kaydara FBX Binary \x00(file - magic, with 2 spaces at the end, then a NULL terminator). + // Bytes 21 - 22: [0x1A, 0x00](unknown but all observed files show these bytes). + // Bytes 23 - 26 : unsigned int, the version number. 7300 for version 7.3 for example. + const int HEADER_BEFORE_VERSION = 23; + const quint32 VERSION_FBX2016 = 7500; + in.skipRawData(HEADER_BEFORE_VERSION); + int position = HEADER_BEFORE_VERSION; + quint32 fileVersion; + in >> fileVersion; + position += sizeof(fileVersion); + qDebug() << "fileVersion:" << fileVersion; + bool has64BitPositions = (fileVersion >= VERSION_FBX2016); // parse the top-level node FBXNode top; while (device->bytesAvailable()) { - FBXNode next = parseBinaryFBXNode(in, position); + FBXNode next = parseBinaryFBXNode(in, position, has64BitPositions); if (next.name.isNull()) { return top; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 6760d44244..c9c9d73394 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -63,15 +63,31 @@ QUrl AddressManager::currentAddress() const { } QUrl AddressManager::currentFacingAddress() const { - QUrl hifiURL; + auto hifiURL = currentAddress(); + hifiURL.setPath(currentFacingPath()); - hifiURL.setScheme(HIFI_URL_SCHEME); - hifiURL.setHost(_host); + return hifiURL; +} - if (_port != 0 && _port != DEFAULT_DOMAIN_SERVER_PORT) { - hifiURL.setPort(_port); + +QUrl AddressManager::currentShareableAddress() const { + if (!_shareablePlaceName.isEmpty()) { + // if we have a shareable place name use that instead of whatever the current host is + QUrl hifiURL; + + hifiURL.setScheme(HIFI_URL_SCHEME); + hifiURL.setHost(_shareablePlaceName); + + hifiURL.setPath(currentPath()); + + return hifiURL; + } else { + return currentAddress(); } +} +QUrl AddressManager::currentFacingShareableAddress() const { + auto hifiURL = currentShareableAddress(); hifiURL.setPath(currentFacingPath()); return hifiURL; @@ -360,6 +376,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const LookupTrigger trigger = (LookupTrigger) reply.property(LOOKUP_TRIGGER_KEY).toInt(); + // set our current root place id to the ID that came back const QString PLACE_ID_KEY = "id"; _rootPlaceID = rootMap[PLACE_ID_KEY].toUuid(); @@ -368,6 +385,18 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); + if (placeName.isEmpty()) { + // we didn't get a set place name, check if there is a default or temporary domain name to use + const QString TEMPORARY_DOMAIN_NAME_KEY = "name"; + const QString DEFAULT_DOMAIN_NAME_KEY = "default_place_name"; + + if (domainObject.contains(TEMPORARY_DOMAIN_NAME_KEY)) { + placeName = domainObject[TEMPORARY_DOMAIN_NAME_KEY].toString(); + } else if (domainObject.contains(DEFAULT_DOMAIN_NAME_KEY)) { + placeName = domainObject[DEFAULT_DOMAIN_NAME_KEY].toString(); + } + } + if (!placeName.isEmpty()) { if (setHost(placeName, trigger)) { trigger = LookupTrigger::Internal; @@ -651,6 +680,9 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 _port = port; + // any host change should clear the shareable place name + _shareablePlaceName.clear(); + if (host != _host) { _host = host; emit hostChanged(_host); @@ -701,13 +733,67 @@ void AddressManager::refreshPreviousLookup() { } void AddressManager::copyAddress() { - QApplication::clipboard()->setText(currentAddress().toString()); + // assume that the address is being copied because the user wants a shareable address + QApplication::clipboard()->setText(currentShareableAddress().toString()); } void AddressManager::copyPath() { QApplication::clipboard()->setText(currentPath()); } +void AddressManager::handleShareableNameAPIResponse(QNetworkReply& requestReply) { + // make sure that this response is for the domain we're currently connected to + auto domainID = DependencyManager::get()->getDomainHandler().getUUID(); + + if (requestReply.url().toString().contains(uuidStringWithoutCurlyBraces(domainID))) { + // check for a name or default name in the API response + + QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject domainObject = responseObject["domain"].toObject(); + + const QString DOMAIN_NAME_KEY = "name"; + const QString DOMAIN_DEFAULT_PLACE_NAME_KEY = "default_place_name"; + + bool shareableNameChanged { false }; + + if (domainObject[DOMAIN_NAME_KEY].isString()) { + _shareablePlaceName = domainObject[DOMAIN_NAME_KEY].toString(); + shareableNameChanged = true; + } else if (domainObject[DOMAIN_DEFAULT_PLACE_NAME_KEY].isString()) { + _shareablePlaceName = domainObject[DOMAIN_DEFAULT_PLACE_NAME_KEY].toString(); + shareableNameChanged = true; + } + + if (shareableNameChanged) { + qDebug() << "AddressManager shareable name changed to" << _shareablePlaceName; + } + } +} + +void AddressManager::lookupShareableNameForDomainID(const QUuid& domainID) { + + // if we get to a domain via IP/hostname, often the address is only reachable by this client + // and not by other clients on the LAN or Internet + + // to work around this we use the ID to lookup the default place name, and if it exists we + // then use that for Steam join/invite or copiable address + + // it only makes sense to lookup a shareable default name if we don't have a place name + if (_placeName.isEmpty()) { + JSONCallbackParameters callbackParams; + + // no error callback handling + // in the case of an error we simply assume there is no default place name + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "handleShareableNameAPIResponse"; + + DependencyManager::get()->sendRequest(GET_DOMAIN_ID.arg(uuidStringWithoutCurlyBraces(domainID)), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, + callbackParams); + } +} + void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 23df176d8b..248a1ef435 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -60,6 +60,8 @@ public: QUrl currentAddress() const; QUrl currentFacingAddress() const; + QUrl currentShareableAddress() const; + QUrl currentFacingShareableAddress() const; QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; @@ -102,6 +104,8 @@ public slots: void copyAddress(); void copyPath(); + void lookupShareableNameForDomainID(const QUuid& domainID); + signals: void lookupResultsFinished(); void lookupResultIsOffline(); @@ -125,6 +129,8 @@ private slots: void handleAPIResponse(QNetworkReply& requestReply); void handleAPIError(QNetworkReply& errorReply); + void handleShareableNameAPIResponse(QNetworkReply& requestReply); + private: void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); @@ -155,6 +161,8 @@ private: PositionGetter _positionGetter; OrientationGetter _orientationGetter; + QString _shareablePlaceName; + QStack _backStack; QStack _forwardStack; quint64 _lastBackPush = 0; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 781cc00c1c..3a07ea8b54 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -539,6 +539,10 @@ void NodeList::processDomainServerList(QSharedPointer message) if (!_domainHandler.isConnected()) { _domainHandler.setUUID(domainUUID); _domainHandler.setIsConnected(true); + + // in case we didn't use a place name to get to this domain, + // give the address manager a chance to lookup a default one now + DependencyManager::get()->lookupShareableNameForDomainID(domainUUID); } else if (_domainHandler.getUUID() != domainUUID) { // Recieved packet from different domain. qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" << _domainHandler.getUUID(); diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp index 66289a35dd..ed9acb9ada 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/shared/src/PointerEvent.cpp @@ -81,28 +81,43 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve direction.setProperty("z", event._direction.z); obj.setProperty("pos3D", direction); + bool isPrimaryButton = false; + bool isSecondaryButton = false; + bool isTertiaryButton = false; switch (event._button) { case NoButtons: obj.setProperty("button", "None"); break; case PrimaryButton: obj.setProperty("button", "Primary"); + isPrimaryButton = true; break; case SecondaryButton: obj.setProperty("button", "Secondary"); + isSecondaryButton = true; break; case TertiaryButton: obj.setProperty("button", "Tertiary"); + isTertiaryButton = true; break; } - obj.setProperty("isLeftButton", areFlagsSet(event._buttons, PrimaryButton)); - obj.setProperty("isRightButton", areFlagsSet(event._buttons, SecondaryButton)); - obj.setProperty("isMiddleButton", areFlagsSet(event._buttons, TertiaryButton)); + if (isPrimaryButton) { + obj.setProperty("isPrimaryButton", isPrimaryButton); + obj.setProperty("isLeftButton", isPrimaryButton); + } + if (isSecondaryButton) { + obj.setProperty("isSecondaryButton", isSecondaryButton); + obj.setProperty("isRightButton", isSecondaryButton); + } + if (isTertiaryButton) { + obj.setProperty("isTertiaryButton", isTertiaryButton); + obj.setProperty("isMiddleButton", isTertiaryButton); + } - obj.setProperty("isPrimaryButton", areFlagsSet(event._buttons, PrimaryButton)); - obj.setProperty("isSecondaryButton", areFlagsSet(event._buttons, SecondaryButton)); - obj.setProperty("isTertiaryButton", areFlagsSet(event._buttons, TertiaryButton)); + obj.setProperty("isPrimaryHeld", areFlagsSet(event._buttons, PrimaryButton)); + obj.setProperty("isSecondaryHeld", areFlagsSet(event._buttons, SecondaryButton)); + obj.setProperty("isTertiaryHeld", areFlagsSet(event._buttons, TertiaryButton)); return obj; } @@ -146,9 +161,9 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve event._button = NoButtons; } - bool primary = object.property("isPrimary").toBool() || object.property("isLeftButton").toBool(); - bool secondary = object.property("isSecondary").toBool() || object.property("isRightButton").toBool(); - bool tertiary = object.property("isTertiary").toBool() || object.property("isMiddleButton").toBool(); + bool primary = object.property("isPrimaryHeld").toBool(); + bool secondary = object.property("isSecondaryHeld").toBool(); + bool tertiary = object.property("isTertiaryHeld").toBool(); event._buttons = 0; if (primary) { event._buttons |= PrimaryButton; diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 32e0b047de..0f6a1434fc 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1319,6 +1319,11 @@ function MyController(hand) { this.searchEnter = function() { mostRecentSearchingHand = this.hand; + var rayPickInfo = this.calcRayPickInfo(this.hand); + if (rayPickInfo.entityID || rayPickInfo.overlayID) { + this.intersectionDistance = rayPickInfo.distance; + this.searchSphereDistance = this.intersectionDistance; + } }; this.search = function(deltaTime, timestamp) { @@ -1426,10 +1431,7 @@ function MyController(hand) { pos3D: rayPickInfo.intersection, normal: rayPickInfo.normal, direction: rayPickInfo.searchRay.direction, - button: "None", - isPrimaryButton: false, - isSecondaryButton: false, - isTertiaryButton: false + button: "None" }; this.hoverEntity = entity; @@ -1449,10 +1451,7 @@ function MyController(hand) { pos3D: rayPickInfo.intersection, normal: rayPickInfo.normal, direction: rayPickInfo.searchRay.direction, - button: "None", - isPrimaryButton: false, - isSecondaryButton: false, - isTertiaryButton: false + button: "None" }; Entities.sendMouseMoveOnEntity(entity, pointerEvent); @@ -2124,9 +2123,7 @@ function MyController(hand) { normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, button: "Primary", - isPrimaryButton: true, - isSecondaryButton: false, - isTertiaryButton: false + isPrimaryHeld: true }; Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent); @@ -2152,15 +2149,12 @@ function MyController(hand) { pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, - button: "Primary", - isPrimaryButton: false, - isSecondaryButton: false, - isTertiaryButton: false + button: "Primary" }; } else { pointerEvent = this.touchingEnterPointerEvent; pointerEvent.button = "Primary"; - pointerEvent.isPrimaryButton = false; + pointerEvent.isPrimaryHeld = false; } Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent); @@ -2197,9 +2191,7 @@ function MyController(hand) { normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, button: "NoButtons", - isPrimaryButton: true, - isSecondaryButton: false, - isTertiaryButton: false + isPrimaryHeld: true }; var POINTER_PRESS_TO_MOVE_DELAY = 0.15; // seconds