diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 9816cebf43..3e93981ed3 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -82,6 +82,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); + packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -367,10 +368,13 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { return; } - bool sendIdentity = false; - if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) { - AvatarData& avatar = nodeData->getAvatar(); - const QString& existingBaseDisplayName = nodeData->getAvatar().getSessionDisplayName(); + MixerAvatar& avatar = nodeData->getAvatar(); + bool sendIdentity = avatar.needsIdentityUpdate(); + if (sendIdentity) { + nodeData->flagIdentityChange(); + } + if (nodeData->getAvatarSessionDisplayNameMustChange()) { + const QString& existingBaseDisplayName = avatar.getSessionDisplayName(); if (!existingBaseDisplayName.isEmpty()) { SessionDisplayName existingDisplayName { existingBaseDisplayName }; @@ -414,10 +418,11 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar. // since this packet includes a change to either the skeleton model URL or the display name // it needs a new sequence number - nodeData->getAvatar().pushIdentitySequenceNumber(); + avatar.pushIdentitySequenceNumber(); // tell node whose name changed about its new session display name or avatar. sendIdentityPacket(nodeData, node); + avatar.setNeedsIdentityUpdate(false); } } @@ -1123,6 +1128,16 @@ void AvatarMixer::entityChange() { _dirtyHeroStatus = true; } +void AvatarMixer::handleChallengeOwnership(QSharedPointer message, SharedNodePointer senderNode) { + if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) { + auto clientData = static_cast(senderNode->getLinkedData()); + auto avatar = clientData->getAvatarSharedPointer(); + if (avatar) { + avatar->handleChallengeResponse(message.data()); + } + } +} + void AvatarMixer::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 10dff5e8a4..93dc755f51 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -65,6 +65,7 @@ private slots: void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); + void handleChallengeOwnership(QSharedPointer message, SharedNodePointer senderNode); void start(); private: diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 4be9f4f46f..cdd639ed80 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -81,6 +81,10 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData } assert(_packetQueue.empty()); + if (_avatar) { + _avatar->processCertifyEvents(); + } + return packetsProcessed; } @@ -200,6 +204,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, if (traitType == AvatarTraits::SkeletonModelURL) { // special handling for skeleton model URL, since we need to make sure it is in the whitelist checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion); + _avatar->fetchAvatarFST(); } anyTraitsChanged = true; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 32c944f5b8..64f4aa6821 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -157,6 +157,11 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis ++simpleReceivedIt; } + if (bytesWritten > 0 && sendingAvatar->isCertifyFailed()) { + // Resend identity packet if certification failed: + sendingAvatar->setNeedsIdentityUpdate(); + } + // enumerate the received instanced trait versions auto instancedReceivedIt = lastReceivedVersions.instancedCBegin(); while (instancedReceivedIt != lastReceivedVersions.instancedCEnd()) { diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp new file mode 100644 index 0000000000..8f5c60a7d9 --- /dev/null +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -0,0 +1,345 @@ +// +// MixerAvatar.cpp +// assignment-client/src/avatars +// +// Created by Simon Walton April 2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MixerAvatar.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "ClientTraitsHandler.h" +#include "AvatarLogging.h" + +void MixerAvatar::fetchAvatarFST() { + _verifyState = nonCertified; + + _pendingEvent = false; + + QUrl avatarURL = getSkeletonModelURL(); + if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") { + // Not network FST. + return; + } + _certificateIdFromURL.clear(); + _certificateIdFromFST.clear(); + _marketplaceIdFromURL.clear(); + _marketplaceIdFromFST.clear(); + auto resourceManager = DependencyManager::get(); + + // Match UUID + (optionally) URL cert + static const QRegularExpression marketIdRegex{ + "^https://.*?highfidelity\\.com/api/.*?/commerce/entity_edition/([-0-9a-z]{36})(.*?certificate_id=([\\w/+%]+)|.*).*$" + }; + auto marketIdMatch = marketIdRegex.match(avatarURL.toDisplayString()); + if (marketIdMatch.hasMatch()) { + QMutexLocker certifyLocker(&_avatarCertifyLock); + _marketplaceIdFromURL = marketIdMatch.captured(1); + if (marketIdMatch.lastCapturedIndex() == 3) { + _certificateIdFromURL = QUrl::fromPercentEncoding(marketIdMatch.captured(3).toUtf8()); + } + } + + ResourceRequest* fstRequest = resourceManager->createResourceRequest(this, avatarURL); + if (fstRequest) { + QMutexLocker certifyLocker(&_avatarCertifyLock); + + _avatarRequest = fstRequest; + _verifyState = requestingFST; + connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete); + fstRequest->send(); + } else { + qCDebug(avatars) << "Couldn't create FST request for" << avatarURL; + _verifyState = error; + } + _needsIdentityUpdate = true; +} + +void MixerAvatar::fstRequestComplete() { + ResourceRequest* fstRequest = static_cast(QObject::sender()); + QMutexLocker certifyLocker(&_avatarCertifyLock); + if (fstRequest == _avatarRequest) { + auto result = fstRequest->getResult(); + if (result != ResourceRequest::Success) { + _verifyState = error; + qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result; + } else { + _avatarFSTContents = fstRequest->getData(); + _verifyState = receivedFST; + _pendingEvent = true; + } + _avatarRequest->deleteLater(); + _avatarRequest = nullptr; + } else { + qCDebug(avatars) << "Incorrect request for" << getDisplayName(); + } +} + +bool MixerAvatar::generateFSTHash() { + if (_avatarFSTContents.length() == 0) { + return false; + } + QByteArray hashJson = canonicalJson(_avatarFSTContents); + QCryptographicHash fstHash(QCryptographicHash::Sha256); + fstHash.addData(hashJson); + _certificateHash = fstHash.result(); + return true; +} + +bool MixerAvatar::validateFSTHash(const QString& publicKey) { + // Guess we should refactor this stuff into a Authorization namespace ... + return EntityItemProperties::verifySignature(publicKey, _certificateHash, + QByteArray::fromBase64(_certificateIdFromFST.toUtf8())); +} + +QByteArray MixerAvatar::canonicalJson(const QString fstFile) { + QStringList fstLines = fstFile.split("\n", QString::SkipEmptyParts); + static const QString fstKeywordsReg { + "(marketplaceID|itemDescription|itemCategories|itemArtist|itemLicenseUrl|limitedRun|itemName|" + "filename|texdir|script|editionNumber|certificateID)" + }; + QRegularExpression fstLineRegExp { QString("^\\s*") + fstKeywordsReg + "\\s*=\\s*(\\S.*)$" }; + QStringListIterator fstLineIter(fstLines); + + QJsonObject certifiedItems; + QStringList scripts; + while (fstLineIter.hasNext()) { + auto line = fstLineIter.next(); + auto lineMatch = fstLineRegExp.match(line); + if (lineMatch.hasMatch()) { + QString key = lineMatch.captured(1); + if (key == "certificateID") { + _certificateIdFromFST = lineMatch.captured(2); + } else if (key == "itemDescription") { + // Item description can be multiline - intermediate lines end in + QString itemDesc = lineMatch.captured(2); + while (itemDesc.endsWith('\r') && fstLineIter.hasNext()) { + itemDesc += '\n' + fstLineIter.next(); + } + certifiedItems[key] = QJsonValue(itemDesc); + } else if (key == "limitedRun" || key == "editionNumber") { + double value = lineMatch.captured(2).toDouble(); + if (value != 0.0) { + certifiedItems[key] = QJsonValue(value); + } + } else if (key == "script") { + scripts.append(lineMatch.captured(2).trimmed()); + } else { + certifiedItems[key] = QJsonValue(lineMatch.captured(2)); + if (key == "marketplaceID") { + _marketplaceIdFromFST = lineMatch.captured(2); + } + } + } + } + if (!scripts.empty()) { + scripts.sort(); + certifiedItems["script"] = QJsonArray::fromStringList(scripts); + } + + QJsonDocument jsonDocCertifiedItems(certifiedItems); + //Example working form: + //return R"({"editionNumber":34,"filename":"http://mpassets.highfidelity.com/7f142fde-541a-4902-b33a-25fa89dfba21-v1/Bridger/Hifi_Toon_Male_3.fbx","itemArtist":"EgyMax", + //"itemCategories":"Avatars","itemDescription":"This is my first avatar. I hope you like it. More will come","itemName":"Bridger","limitedRun":-1, + //"marketplaceID":"7f142fde-541a-4902-b33a-25fa89dfba21","texdir":"http://mpassets.highfidelity.com/7f142fde-541a-4902-b33a-25fa89dfba21-v1/Bridger/textures"})"; + return jsonDocCertifiedItems.toJson(QJsonDocument::Compact); +} + +void MixerAvatar::ownerRequestComplete() { + QMutexLocker certifyLocker(&_avatarCertifyLock); + QNetworkReply* networkReply = static_cast(QObject::sender()); + + if (networkReply->error() == QNetworkReply::NoError) { + _dynamicMarketResponse = networkReply->readAll(); + _verifyState = ownerResponse; + _pendingEvent = true; + } else { + auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"]; + if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) { + qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":" + << jsonData.toObject()["message"].toString(); + _verifyState = error; + _pendingEvent = false; + } + } + networkReply->deleteLater(); +} + +void MixerAvatar::processCertifyEvents() { + if (!_pendingEvent) { + return; + } + + QMutexLocker certifyLocker(&_avatarCertifyLock); + switch (_verifyState) { + + case receivedFST: + { + generateFSTHash(); + if (_certificateIdFromFST.length() != 0) { + QString& marketplacePublicKey = EntityItem::_marketplacePublicKey; + bool staticVerification = validateFSTHash(marketplacePublicKey); + _verifyState = staticVerification ? staticValidation : verificationFailed; + + if (_verifyState == staticValidation) { + static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" }; + auto& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath(POP_MARKETPLACE_API); + networkRequest.setUrl(requestURL); + + QJsonObject request; + request["certificate_id"] = _certificateIdFromFST; + _verifyState = requestingOwner; + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete); + } else { + _needsIdentityUpdate = true; + _pendingEvent = false; + qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification"; + } + } else { // FST doesn't have a certificate, so noncertified rather than failed: + _pendingEvent = false; + _verifyState = nonCertified; + } + break; + } + + case ownerResponse: + { + QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8()); + QString ownerPublicKey; + bool ownerValid = false; + if (responseJson["status"].toString() == "success") { + QJsonValue jsonData = responseJson["data"]; + if (jsonData.isObject()) { + auto ownerJson = jsonData["transfer_recipient_key"]; + if (ownerJson.isString()) { + ownerPublicKey = ownerJson.toString(); + } + auto transferStatusJson = jsonData["transfer_status"]; + if (transferStatusJson.isArray() && transferStatusJson.toArray()[0].toString() == "confirmed") { + ownerValid = true; + } + } + if (ownerValid && !ownerPublicKey.isEmpty()) { + if (ownerPublicKey.startsWith("-----BEGIN ")){ + _ownerPublicKey = ownerPublicKey; + } else { + _ownerPublicKey = "-----BEGIN PUBLIC KEY-----\n" + + ownerPublicKey + + "\n-----END PUBLIC KEY-----\n"; + } + sendOwnerChallenge(); + _verifyState = challengeClient; + } else { + _verifyState = error; + } + } else { + qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL << + "message:" << responseJson["message"].toString(); + _verifyState = error; + } + _pendingEvent = false; + break; + } + + case challengeResponse: + { + if (_challengeResponse.length() < 8) { + _verifyState = error; + _pendingEvent = false; + break; + } + + int avatarIDLength; + int signedNonceLength; + { + QDataStream responseStream(_challengeResponse); + responseStream.setByteOrder(QDataStream::LittleEndian); + responseStream >> avatarIDLength >> signedNonceLength; + } + QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength); + QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); + + bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, + QByteArray::fromBase64(signedNonce)); + _verifyState = challengeResult ? verificationSucceeded : verificationFailed; + _needsIdentityUpdate = true; + if (_verifyState == verificationFailed) { + qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); + } else { + qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID(); + } + _pendingEvent = false; + break; + } + + case requestingOwner: + { // Qt networking done on this thread: + QCoreApplication::processEvents(); + break; + } + + default: + qCDebug(avatars) << "Unexpected verify state" << _verifyState; + break; + + } // close switch +} + +void MixerAvatar::sendOwnerChallenge() { + auto nodeList = DependencyManager::get(); + QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8(); + QByteArray nonce = QUuid::createUuid().toByteArray(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, + 2 * sizeof(int) + nonce.length() + avatarID.length(), true); + challengeOwnershipPacket->writePrimitive(avatarID.length()); + challengeOwnershipPacket->writePrimitive(nonce.length()); + challengeOwnershipPacket->write(avatarID); + challengeOwnershipPacket->write(nonce); + + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) ); + QCryptographicHash nonceHash(QCryptographicHash::Sha256); + nonceHash.addData(nonce); + _challengeNonceHash = nonceHash.result(); + + static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s + _challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS); + _challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() { + _verifyState = verificationFailed; + _needsIdentityUpdate = true; + }); +} + +void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) { + QByteArray avatarID; + QByteArray encryptedNonce; + QMutexLocker certifyLocker(&_avatarCertifyLock); + if (_verifyState == challengeClient) { + _challengeTimeout.stop(); + _challengeResponse = response->readAll(); + _verifyState = challengeResponse; + _pendingEvent = true; + } +} diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 01e5e91b44..bafc398a02 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -17,14 +17,55 @@ #include +class ResourceRequest; + class MixerAvatar : public AvatarData { public: bool getNeedsHeroCheck() const { return _needsHeroCheck; } - void setNeedsHeroCheck(bool needsHeroCheck = true) - { _needsHeroCheck = needsHeroCheck; } + void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; } + + void fetchAvatarFST(); + virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; } + bool needsIdentityUpdate() const { return _needsIdentityUpdate; } + void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; } + + void processCertifyEvents(); + void handleChallengeResponse(ReceivedMessage* response); private: bool _needsHeroCheck { false }; + + // Avatar certification/verification: + enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse, + challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error }; + Q_ENUM(VerifyState); + VerifyState _verifyState { nonCertified }; + std::atomic _pendingEvent { false }; + QMutex _avatarCertifyLock; + ResourceRequest* _avatarRequest { nullptr }; + QString _marketplaceIdFromURL; + QString _marketplaceIdFromFST; + QByteArray _avatarFSTContents; + QByteArray _certificateHash; + QString _certificateIdFromURL; + QString _certificateIdFromFST; + QString _dynamicMarketResponse; + QString _ownerPublicKey; + QByteArray _challengeNonceHash; + QByteArray _challengeResponse; + QTimer _challengeTimeout; + bool _needsIdentityUpdate { false }; + + bool generateFSTHash(); + bool validateFSTHash(const QString& publicKey); + QByteArray canonicalJson(const QString fstFile); + void sendOwnerChallenge(); + + static const QString VERIFY_FAIL_MODEL; + +private slots: + void fstRequestComplete(); + void ownerRequestComplete(); }; using MixerAvatarSharedPointer = std::shared_ptr; diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index bc35d2f2f8..3615b80cda 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -89,7 +89,7 @@ if (APPLE) string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION}) message(STATUS "Detected OS X version = ${OSX_VERSION}") - set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") + set(OSX_SDK "${OSX_VERSION}" CACHE STRING "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") # set our OS X deployment target set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9) diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index 481753f7e0..53c4c2976c 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -27,12 +27,12 @@ if (WIN32) ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) set(LIBOVR_DIR ${INSTALL_DIR}) - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${LIBOVR_DIR}/Lib/LibOVRd.lib CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${LIBOVR_DIR}/Lib/LibOVR.lib CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${LIBOVR_DIR}/Lib/LibOVRd.lib CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${LIBOVR_DIR}/Lib/LibOVR.lib CACHE STRING INTERNAL) include(SelectLibraryConfigurations) select_library_configurations(LIBOVR) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE STRING INTERNAL) elseif(APPLE) @@ -50,11 +50,11 @@ elseif(APPLE) # In theory we should use the Headers path inside the framework, as seen here # but unfortunately Oculus doesn't seem to have figured out automated testing # so they released a framework with missing headers. - #set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE TYPE INTERNAL) + #set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE STRING INTERNAL) # Work around the broken framework by using a different path for the headers. - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE STRING INTERNAL) @@ -74,8 +74,8 @@ elseif(NOT ANDROID) ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE STRING INTERNAL) find_package(Threads REQUIRED) find_package(X11 REQUIRED) @@ -89,8 +89,8 @@ elseif(NOT ANDROID) select_library_configurations(${EXTERNAL_NAME_UPPER}) - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE STRING INTERNAL) endif() # Hide this external target (for ide users) diff --git a/cmake/externals/LibOVRPlatform/CMakeLists.txt b/cmake/externals/LibOVRPlatform/CMakeLists.txt index 895efa9357..adf89823f2 100644 --- a/cmake/externals/LibOVRPlatform/CMakeLists.txt +++ b/cmake/externals/LibOVRPlatform/CMakeLists.txt @@ -20,12 +20,12 @@ if (WIN32) ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE STRING INTERNAL) else() - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE STRING INTERNAL) endif() - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE STRING INTERNAL) endif () # Hide this external target (for ide users) diff --git a/cmake/externals/hifiAudioCodec/CMakeLists.txt b/cmake/externals/hifiAudioCodec/CMakeLists.txt index 8a8e2573d5..5d439cd519 100644 --- a/cmake/externals/hifiAudioCodec/CMakeLists.txt +++ b/cmake/externals/hifiAudioCodec/CMakeLists.txt @@ -36,10 +36,10 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING INTERNAL) if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE STRING INTERNAL) else() - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE STRING INTERNAL) endif() diff --git a/cmake/externals/neuron/CMakeLists.txt b/cmake/externals/neuron/CMakeLists.txt index 5ac38bc442..049e175354 100644 --- a/cmake/externals/neuron/CMakeLists.txt +++ b/cmake/externals/neuron/CMakeLists.txt @@ -21,9 +21,9 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") # set include dir if(WIN32) - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE STRING INTERNAL) elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE STRING INTERNAL) else() # Unsupported endif() @@ -37,16 +37,16 @@ if(WIN32) endif() set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Windows/lib/${ARCH_DIR}") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE STRING INTERNAL) add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}") elseif(APPLE) set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Mac/dylib") - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE STRING INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE STRING INTERNAL) add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}") diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt index 17d2f98e2d..35f7758820 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/sixense/CMakeLists.txt @@ -30,7 +30,7 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING INTERNAL) if (WIN32) @@ -52,7 +52,7 @@ if (WIN32) set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll") endif() - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE STRING INTERNAL) add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}") elseif(APPLE) @@ -62,7 +62,7 @@ elseif(APPLE) elseif(NOT ANDROID) # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE STRING INTERNAL) endif() diff --git a/cmake/externals/steamworks/CMakeLists.txt b/cmake/externals/steamworks/CMakeLists.txt index 30b3926436..eb61d22f8b 100644 --- a/cmake/externals/steamworks/CMakeLists.txt +++ b/cmake/externals/steamworks/CMakeLists.txt @@ -21,7 +21,7 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE TYPE INTERNAL) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE STRING INTERNAL) if (WIN32) @@ -36,12 +36,12 @@ if (WIN32) set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${ARCH_DIR}) set(${EXTERNAL_NAME_UPPER}_LIB_PATH ${ARCH_DIR}) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE STRING INTERNAL) add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}") elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE STRING INTERNAL) set(_STEAMWORKS_LIB_DIR "${SOURCE_DIR}/redistributable_bin/osx32") ExternalProject_Add_Step( @@ -57,6 +57,6 @@ elseif(APPLE) elseif(NOT ANDROID) # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE STRING INTERNAL) endif() diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index 436cae79a1..1788922ef2 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -102,6 +102,6 @@ if (DEFINED _TBB_LIB_DIR) endif () if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE) - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories") + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING "List of tbb include directories") endif () diff --git a/cmake/macros/TargetBreakpad.cmake b/cmake/macros/TargetBreakpad.cmake index dac581d6c7..58626ad17b 100644 --- a/cmake/macros/TargetBreakpad.cmake +++ b/cmake/macros/TargetBreakpad.cmake @@ -8,7 +8,7 @@ macro(TARGET_BREAKPAD) if (ANDROID) set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/breakpad) - set(BREAKPAD_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(BREAKPAD_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL) set(LIB_DIR ${INSTALL_DIR}/lib) list(APPEND BREAKPAD_LIBRARIES ${LIB_DIR}/libbreakpad_client.a) target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BREAKPAD_INCLUDE_DIRS}) diff --git a/cmake/macros/TargetBullet.cmake b/cmake/macros/TargetBullet.cmake index 1f4050dd42..843b03ac13 100644 --- a/cmake/macros/TargetBullet.cmake +++ b/cmake/macros/TargetBullet.cmake @@ -8,7 +8,7 @@ macro(TARGET_BULLET) if (ANDROID) set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/bullet) - set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE TYPE INTERNAL) + set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE STRING INTERNAL) set(LIB_DIR ${INSTALL_DIR}/lib) list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libBulletDynamics.a) diff --git a/cmake/macros/TargetDraco.cmake b/cmake/macros/TargetDraco.cmake index 9dbfa865b8..520786d4c3 100755 --- a/cmake/macros/TargetDraco.cmake +++ b/cmake/macros/TargetDraco.cmake @@ -3,7 +3,7 @@ macro(TARGET_DRACO) find_library(LIBPATH ${LIB} PATHS ) if (ANDROID) set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/draco) - set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL) set(LIB_DIR ${INSTALL_DIR}/lib) list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdraco.a) list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdracodec.a) @@ -21,4 +21,4 @@ macro(TARGET_DRACO) select_library_configurations(DRACO) target_link_libraries(${TARGET_NAME} ${DRACO_LIBRARY}) endif() -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/macros/TargetHifiAudioCodec.cmake b/cmake/macros/TargetHifiAudioCodec.cmake index 98c24e684c..4eaccc4f25 100644 --- a/cmake/macros/TargetHifiAudioCodec.cmake +++ b/cmake/macros/TargetHifiAudioCodec.cmake @@ -9,9 +9,9 @@ macro(TARGET_HIFIAUDIOCODEC) if (ANDROID) set(HIFIAC_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/hifiAC/codecSDK) set(HIFIAC_LIB_DIR "${HIFIAC_INSTALL_DIR}/Release") - set(HIFIAC_INCLUDE_DIRS "${HIFIAC_INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(HIFIAC_INCLUDE_DIRS "${HIFIAC_INSTALL_DIR}/include" CACHE STRING INTERNAL) list(APPEND HIFIAC_LIBS "${HIFIAC_LIB_DIR}/libaudio.a") - set(HIFIAC_LIBRARIES ${HIFIAC_LIBS} CACHE TYPE INTERNAL) + set(HIFIAC_LIBRARIES ${HIFIAC_LIBS} CACHE STRING INTERNAL) else() add_dependency_external_projects(hifiAudioCodec) target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS}) diff --git a/cmake/macros/TargetNvtt.cmake b/cmake/macros/TargetNvtt.cmake index 8227355cb9..c2f243563f 100644 --- a/cmake/macros/TargetNvtt.cmake +++ b/cmake/macros/TargetNvtt.cmake @@ -9,12 +9,12 @@ macro(TARGET_NVTT) if (ANDROID) set(NVTT_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/nvtt) set(NVTT_LIB_DIR "${NVTT_INSTALL_DIR}/lib") - set(NVTT_INCLUDE_DIRS "${NVTT_INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(NVTT_INCLUDE_DIRS "${NVTT_INSTALL_DIR}/include" CACHE STRING INTERNAL) list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvcore.so") list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvmath.so") list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvimage.so") list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvtt.so") - set(NVTT_LIBRARIES ${NVTT_LIBS} CACHE TYPE INTERNAL) + set(NVTT_LIBRARIES ${NVTT_LIBS} CACHE STRING INTERNAL) target_include_directories(${TARGET_NAME} PRIVATE ${NVTT_INCLUDE_DIRS}) else() find_library(NVTT_LIBRARY_RELEASE nvtt PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH) diff --git a/cmake/macros/TargetOpenSSL.cmake b/cmake/macros/TargetOpenSSL.cmake index 3faaab5801..f74015201d 100644 --- a/cmake/macros/TargetOpenSSL.cmake +++ b/cmake/macros/TargetOpenSSL.cmake @@ -8,8 +8,8 @@ macro(TARGET_OPENSSL) if (ANDROID) set(OPENSSL_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/openssl) - set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE TYPE INTERNAL) - set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE TYPE INTERNAL) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE STRING INTERNAL) + set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE STRING INTERNAL) else() # using VCPKG for OpenSSL find_package(OpenSSL REQUIRED) diff --git a/cmake/macros/TargetPolyvox.cmake b/cmake/macros/TargetPolyvox.cmake index 9db6b522c7..b2c4e30dd2 100644 --- a/cmake/macros/TargetPolyvox.cmake +++ b/cmake/macros/TargetPolyvox.cmake @@ -8,7 +8,7 @@ macro(TARGET_POLYVOX) if (ANDROID) set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/polyvox) - set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL) set(LIB_DIR ${INSTALL_DIR}/lib) list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/libPolyVoxUtil.so) list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/Release/libPolyVoxCore.so) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index fbbbec9cf2..55a6d3f645 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1357,8 +1357,8 @@ "name": "connection_rate", "label": "Connection Rate", "help": "Number of new agents that can connect to the mixer every second", - "placeholder": "50", - "default": "50", + "placeholder": "10000000", + "default": "10000000", "advanced": true }, { diff --git a/interface/resources/images/AvatarTheftBanner.png b/interface/resources/images/AvatarTheftBanner.png new file mode 100644 index 0000000000..3dc76999e0 Binary files /dev/null and b/interface/resources/images/AvatarTheftBanner.png differ diff --git a/interface/resources/meshes/mannequin/man_stolen.fbx b/interface/resources/meshes/mannequin/man_stolen.fbx new file mode 100644 index 0000000000..739c0efe91 Binary files /dev/null and b/interface/resources/meshes/mannequin/man_stolen.fbx differ diff --git a/interface/resources/meshes/verifyFailed.fst b/interface/resources/meshes/verifyFailed.fst new file mode 100644 index 0000000000..97941bf836 --- /dev/null +++ b/interface/resources/meshes/verifyFailed.fst @@ -0,0 +1,86 @@ +name = mannequin2 +type = body+head +scale = 1 +filename = mannequin/man_stolen.fbx +texdir = textures +joint = jointEyeLeft = LeftEye +joint = jointRightHand = RightHand +joint = jointHead = Head +joint = jointEyeRight = RightEye +joint = jointNeck = Neck +joint = jointLeftHand = LeftHand +joint = jointLean = Spine +joint = jointRoot = Hips +freeJoint = LeftArm +freeJoint = LeftForeArm +freeJoint = RightArm +freeJoint = RightForeArm +jointIndex = RightHandPinky2 = 19 +jointIndex = LeftHandPinky3 = 44 +jointIndex = RightToeBase = 9 +jointIndex = LeftHandRing4 = 49 +jointIndex = LeftHandPinky1 = 42 +jointIndex = LeftHandRing1 = 46 +jointIndex = LeftLeg = 2 +jointIndex = RightHandIndex4 = 29 +jointIndex = LeftHandRing3 = 48 +jointIndex = RightShoulder = 14 +jointIndex = RightArm = 15 +jointIndex = Neck = 62 +jointIndex = RightHandMiddle2 = 35 +jointIndex = HeadTop_End = 66 +jointIndex = LeftHandRing2 = 47 +jointIndex = RightHandThumb1 = 30 +jointIndex = RightHandRing3 = 24 +jointIndex = LeftHandIndex3 = 52 +jointIndex = LeftForeArm = 40 +jointIndex = face = 68 +jointIndex = LeftToe_End = 5 +jointIndex = RightHandThumb3 = 32 +jointIndex = RightEye = 65 +jointIndex = Spine = 11 +jointIndex = LeftEye = 64 +jointIndex = LeftToeBase = 4 +jointIndex = LeftHandIndex4 = 53 +jointIndex = RightHandPinky4 = 21 +jointIndex = RightHandMiddle1 = 34 +jointIndex = Spine1 = 12 +jointIndex = LeftHandIndex2 = 51 +jointIndex = RightToe_End = 10 +jointIndex = RightHand = 17 +jointIndex = LeftUpLeg = 1 +jointIndex = RightHandRing1 = 22 +jointIndex = RightUpLeg = 6 +jointIndex = RightHandMiddle4 = 37 +jointIndex = Head = 63 +jointIndex = RightHandMiddle3 = 36 +jointIndex = RightHandIndex1 = 26 +jointIndex = LeftHandMiddle4 = 61 +jointIndex = LeftHandPinky4 = 45 +jointIndex = Hips = 0 +jointIndex = body = 67 +jointIndex = RightHandThumb2 = 31 +jointIndex = LeftHandThumb2 = 55 +jointIndex = RightHandThumb4 = 33 +jointIndex = RightHandPinky3 = 20 +jointIndex = LeftHandPinky2 = 43 +jointIndex = LeftShoulder = 38 +jointIndex = RightHandIndex3 = 28 +jointIndex = LeftHandThumb4 = 57 +jointIndex = RightLeg = 7 +jointIndex = RightHandIndex2 = 27 +jointIndex = LeftHandMiddle3 = 60 +jointIndex = RightHandRing4 = 25 +jointIndex = LeftHandThumb1 = 54 +jointIndex = LeftArm = 39 +jointIndex = LeftHandThumb3 = 56 +jointIndex = LeftHandMiddle1 = 58 +jointIndex = RightHandPinky1 = 18 +jointIndex = Spine2 = 13 +jointIndex = RightHandRing2 = 23 +jointIndex = RightForeArm = 16 +jointIndex = LeftHandIndex1 = 50 +jointIndex = RightFoot = 8 +jointIndex = LeftHandMiddle2 = 59 +jointIndex = LeftHand = 41 +jointIndex = LeftFoot = 3 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9a2d320329..46b7017180 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -664,9 +664,10 @@ private: /**jsdoc *

The Controller.Hardware.Application object has properties representing Interface's state. The property - * values are integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or - * Controller.Standard items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method). - * Each data value is either 1.0 for "true" or 0.0 for "false".

+ * values are integer IDs, uniquely identifying each output. Read-only.

+ *

These states can be mapped to actions or functions or Controller.Standard items in a {@link RouteObject} + * mapping (e.g., using the {@link RouteObject#when} method). Each data value is either 1.0 for "true" or + * 0.0 for "false".

* * * @@ -680,13 +681,17 @@ private: * * * - * + * + * * * * * * + * + * + * * *
PropertyTypeDataDescription
CameraIndependentnumbernumberThe camera is in independent mode.
CameraEntitynumbernumberThe camera is in entity mode.
InHMDnumbernumberThe user is in HMD mode.
AdvancedMovementnumbernumberAdvanced movement controls are enabled. - *
AdvancedMovementnumbernumberAdvanced movement (walking) controls are + * enabled.
StrafeEnablednumbernumberStrafing is enabled
LeftHandDominantnumbernumberDominant hand set to left.
RightHandDominantnumbernumberDominant hand set to right.
SnapTurnnumbernumberSnap turn is enabled.
GroundednumbernumberThe user's avatar is on the ground.
NavigationFocusednumbernumberNot used.
PlatformWindowsnumbernumberThe operating system is Windows.
PlatformMacnumbernumberThe operating system is Mac.
PlatformAndroidnumbernumberThe operating system is Android.
* @typedef {object} Controller.Hardware-Application @@ -1330,7 +1335,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setCrashAnnotation("avatar", avatarURL.toString().toStdString()); }); - // Inititalize sample before registering _sampleSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/sample.wav")); @@ -1421,6 +1425,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo initializeDisplayPlugins(); qCDebug(interfaceapp, "Initialized Display"); + if (_displayPlugin && !_displayPlugin->isHmd()) { + _preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM)); + showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get())); + } // An audio device changed signal received before the display plugins are set up will cause a crash, // so we defer the setup of the `scripting::Audio` class until this point { @@ -1440,8 +1448,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo audioScriptingInterface->environmentMuted(); } }); - connect(this, &Application::activeDisplayPluginChanged, - reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + QSharedPointer scriptingAudioSharedPointer = qSharedPointerDynamicCast(DependencyManager::get()); + if (scriptingAudioSharedPointer) { + connect(this, &Application::activeDisplayPluginChanged, + scriptingAudioSharedPointer.data(), &scripting::Audio::onContextChanged); + } } // Create the rendering engine. This can be slow on some machines due to lots of @@ -1634,7 +1645,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { using namespace controller; auto tabletScriptingInterface = DependencyManager::get(); - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + QSharedPointer audioScriptingInterface = qSharedPointerDynamicCast(DependencyManager::get()); { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; @@ -1642,10 +1653,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo bool navAxis = false; switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: - if (state > 0.0f) { - audioScriptingInterface->setPushingToTalk(true); - } else if (state <= 0.0f) { - audioScriptingInterface->setPushingToTalk(false); + if (audioScriptingInterface) { + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(true); + } else if (state <= 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } } break; @@ -3377,6 +3390,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data()); if (setAdditionalContextProperties) { + qDebug() << "setting additional context properties!"; auto tabletScriptingInterface = DependencyManager::get(); auto flags = tabletScriptingInterface->getFlags(); @@ -4064,9 +4078,6 @@ bool Application::event(QEvent* event) { case QEvent::KeyRelease: keyReleaseEvent(static_cast(event)); return true; - case QEvent::FocusIn: - focusInEvent(static_cast(event)); - return true; case QEvent::FocusOut: focusOutEvent(static_cast(event)); return true; @@ -4109,6 +4120,11 @@ bool Application::eventFilter(QObject* object, QEvent* event) { return true; } + auto eventType = event->type(); + if (eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease || eventType == QEvent::MouseMove) { + getRefreshRateManager().resetInactiveTimer(); + } + if (event->type() == QEvent::Leave) { getApplicationCompositor().handleLeaveEvent(); } @@ -4298,6 +4314,10 @@ void Application::keyPressEvent(QKeyEvent* event) { if (isMeta) { auto audioClient = DependencyManager::get(); audioClient->setMuted(!audioClient->isMuted()); + QSharedPointer audioScriptingInterface = qSharedPointerDynamicCast(DependencyManager::get()); + if (audioScriptingInterface && audioScriptingInterface->getPTT()) { + audioScriptingInterface->setPushingToTalk(!audioClient->isMuted()); + } } break; @@ -4420,13 +4440,6 @@ void Application::keyReleaseEvent(QKeyEvent* event) { } -void Application::focusInEvent(QFocusEvent* event) { - if (!_aboutToQuit && _startUpFinished) { - getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::RUNNING); - } -} - - void Application::focusOutEvent(QFocusEvent* event) { auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { @@ -4434,10 +4447,6 @@ void Application::focusOutEvent(QFocusEvent* event) { inputPlugin->pluginFocusOutEvent(); } } - - if (!_aboutToQuit && _startUpFinished) { - getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS); - } // FIXME spacemouse code still needs cleanup #if 0 //SpacemouseDevice::getInstance().focusOutEvent(); @@ -5359,8 +5368,10 @@ void Application::loadSettings() { } } - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); - audioScriptingInterface->loadData(); + QSharedPointer audioScriptingInterface = qSharedPointerDynamicCast(DependencyManager::get()); + if (audioScriptingInterface) { + audioScriptingInterface->loadData(); + } getMyAvatar()->loadData(); _settingsLoaded = true; @@ -5371,8 +5382,10 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); - audioScriptingInterface->saveData(); + QSharedPointer audioScriptingInterface = qSharedPointerDynamicCast(DependencyManager::get()); + if (audioScriptingInterface) { + audioScriptingInterface->saveData(); + } Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); @@ -5606,7 +5619,7 @@ void Application::resumeAfterLoginDialogActionTaken() { _myCamera.setMode(_previousCameraMode); cameraModeChanged(); _startUpFinished = true; - getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::RUNNING); + getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE); } void Application::loadAvatarScripts(const QVector& urls) { @@ -8463,28 +8476,30 @@ bool Application::takeSnapshotOperators(std::queue& snapshotOp void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) { addSnapshotOperator(std::make_tuple([notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) { - QString path = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); + qApp->postLambdaEvent([snapshot, notify, includeAnimated, aspectRatio, filename] { + QString path = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); - // If we're not doing an animated snapshot as well... - if (!includeAnimated) { - if (!path.isEmpty()) { - // Tell the dependency manager that the capture of the still snapshot has taken place. - emit DependencyManager::get()->stillSnapshotTaken(path, notify); - } - } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) { - qApp->postLambdaEvent([path, aspectRatio] { + // If we're not doing an animated snapshot as well... + if (!includeAnimated) { + if (!path.isEmpty()) { + // Tell the dependency manager that the capture of the still snapshot has taken place. + emit DependencyManager::get()->stillSnapshotTaken(path, notify); + } + } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) { // Get an animated GIF snapshot and save it SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get()); - }); - } + } + }); }, aspectRatio, true)); } void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { addSnapshotOperator(std::make_tuple([notify, filename](const QImage& snapshot) { - QString snapshotPath = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); + qApp->postLambdaEvent([snapshot, notify, filename] { + QString snapshotPath = DependencyManager::get()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); - emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, notify); + emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, notify); + }); }, 0.0f, false)); } @@ -8549,11 +8564,20 @@ void Application::activeChanged(Qt::ApplicationState state) { switch (state) { case Qt::ApplicationActive: _isForeground = true; + if (!_aboutToQuit && _startUpFinished) { + getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE); + } break; case Qt::ApplicationSuspended: + break; case Qt::ApplicationHidden: + break; case Qt::ApplicationInactive: + if (!_aboutToQuit && _startUpFinished) { + getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS); + } + break; default: _isForeground = false; break; @@ -8884,7 +8908,7 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) { RefreshRateManager& refreshRateManager = getRefreshRateManager(); refreshRateManager.setRefreshRateOperator(OpenGLDisplayPlugin::getRefreshRateOperator()); bool isHmd = newDisplayPlugin->isHmd(); - RefreshRateManager::UXMode uxMode = isHmd ? RefreshRateManager::UXMode::HMD : + RefreshRateManager::UXMode uxMode = isHmd ? RefreshRateManager::UXMode::VR : RefreshRateManager::UXMode::DESKTOP; refreshRateManager.setUXMode(uxMode); diff --git a/interface/src/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp index c4a35da1f6..0c5bcd405e 100644 --- a/interface/src/RefreshRateManager.cpp +++ b/interface/src/RefreshRateManager.cpp @@ -20,37 +20,40 @@ #include -static const int HMD_TARGET_RATE = 90; +static const int VR_TARGET_RATE = 90; static const std::array REFRESH_RATE_PROFILE_TO_STRING = { { "Eco", "Interactive", "Realtime" } }; static const std::array REFRESH_RATE_REGIME_TO_STRING = - { { "Running", "Unfocus", "Minimized", "StartUp", "ShutDown" } }; + { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } }; static const std::array UX_MODE_TO_STRING = - { { "Desktop", "HMD" } }; + { { "Desktop", "VR" } }; static const std::map REFRESH_RATE_PROFILE_FROM_STRING = { { "Eco", RefreshRateManager::RefreshRateProfile::ECO }, { "Interactive", RefreshRateManager::RefreshRateProfile::INTERACTIVE }, { "Realtime", RefreshRateManager::RefreshRateProfile::REALTIME } }; -static const std::array RUNNING_REGIME_PROFILES = - { { 5, 20, 60 } }; -static const std::array UNFOCUS_REGIME_PROFILES = - { { 5, 5, 10 } }; +// Porfile regimes are: +// { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } } -static const std::array MINIMIZED_REGIME_PROFILE = - { { 2, 2, 2 } }; +static const std::array ECO_PROFILE = + { { 20, 10, 5, 2, 30, 30 } }; -static const std::array START_AND_SHUTDOWN_REGIME_PROFILES = - { { 30, 30, 30 } }; +static const std::array INTERACTIVE_PROFILE = + { { 30, 20, 10, 2, 30, 30 } }; -static const std::array, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIMES = - { { RUNNING_REGIME_PROFILES, UNFOCUS_REGIME_PROFILES, MINIMIZED_REGIME_PROFILE, - START_AND_SHUTDOWN_REGIME_PROFILES, START_AND_SHUTDOWN_REGIME_PROFILES } }; +static const std::array REALTIME_PROFILE = + { { 60, 60, 10, 2, 30, 30} }; + +static const std::array, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILES = + { { ECO_PROFILE, INTERACTIVE_PROFILE, REALTIME_PROFILE } }; + + +static const int INACTIVE_TIMER_LIMIT = 3000; std::string RefreshRateManager::refreshRateProfileToString(RefreshRateManager::RefreshRateProfile refreshRateProfile) { @@ -70,14 +73,36 @@ std::string RefreshRateManager::uxModeToString(RefreshRateManager::RefreshRateMa } RefreshRateManager::RefreshRateManager() { - _refreshRateProfile = (RefreshRateManager::RefreshRateProfile) _refreshRateMode.get(); + _refreshRateProfile = (RefreshRateManager::RefreshRateProfile) _refreshRateProfileSetting.get(); + _inactiveTimer->setInterval(INACTIVE_TIMER_LIMIT); + _inactiveTimer->setSingleShot(true); + QObject::connect(_inactiveTimer.get(), &QTimer::timeout, [&] { + toggleInactive(); + }); +} + +void RefreshRateManager::resetInactiveTimer() { + if (_uxMode == RefreshRateManager::UXMode::DESKTOP) { + auto regime = getRefreshRateRegime(); + if (regime == RefreshRateRegime::FOCUS_ACTIVE || regime == RefreshRateRegime::FOCUS_INACTIVE) { + _inactiveTimer->start(); + setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE); + } + } +} + +void RefreshRateManager::toggleInactive() { + if (_uxMode == RefreshRateManager::UXMode::DESKTOP && + getRefreshRateRegime() == RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE) { + setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_INACTIVE); + } } void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRateProfile refreshRateProfile) { if (_refreshRateProfile != refreshRateProfile) { - _refreshRateModeLock.withWriteLock([&] { + _refreshRateProfileSettingLock.withWriteLock([&] { _refreshRateProfile = refreshRateProfile; - _refreshRateMode.set((int) refreshRateProfile); + _refreshRateProfileSetting.set((int) refreshRateProfile); }); updateRefreshRateController(); } @@ -86,9 +111,9 @@ void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRatePr RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile() const { RefreshRateManager::RefreshRateProfile profile = RefreshRateManager::RefreshRateProfile::REALTIME; - if (getUXMode() != RefreshRateManager::UXMode::HMD) { - profile =(RefreshRateManager::RefreshRateProfile) _refreshRateModeLock.resultWithReadLock([&] { - return _refreshRateMode.get(); + if (getUXMode() != RefreshRateManager::UXMode::VR) { + profile =(RefreshRateManager::RefreshRateProfile) _refreshRateProfileSettingLock.resultWithReadLock([&] { + return _refreshRateProfileSetting.get(); }); } @@ -96,8 +121,11 @@ RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile } RefreshRateManager::RefreshRateRegime RefreshRateManager::getRefreshRateRegime() const { - return getUXMode() == RefreshRateManager::UXMode::HMD ? RefreshRateManager::RefreshRateRegime::RUNNING : - _refreshRateRegime; + if (getUXMode() == RefreshRateManager::UXMode::VR) { + return RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE; + } else { + return _refreshRateRegime; + } } void RefreshRateManager::setRefreshRateRegime(RefreshRateManager::RefreshRateRegime refreshRateRegime) { @@ -119,31 +147,12 @@ void RefreshRateManager::updateRefreshRateController() const { if (_refreshRateOperator) { int targetRefreshRate; if (_uxMode == RefreshRateManager::UXMode::DESKTOP) { - if (_refreshRateRegime == RefreshRateManager::RefreshRateRegime::RUNNING && - _refreshRateProfile == RefreshRateManager::RefreshRateProfile::INTERACTIVE) { - targetRefreshRate = getInteractiveRefreshRate(); - } else { - targetRefreshRate = REFRESH_RATE_REGIMES[_refreshRateRegime][_refreshRateProfile]; - } + targetRefreshRate = REFRESH_RATE_PROFILES[_refreshRateProfile][_refreshRateRegime]; } else { - targetRefreshRate = HMD_TARGET_RATE; + targetRefreshRate = VR_TARGET_RATE; } _refreshRateOperator(targetRefreshRate); _activeRefreshRate = targetRefreshRate; } } - -void RefreshRateManager::setInteractiveRefreshRate(int refreshRate) { - _refreshRateLock.withWriteLock([&] { - _interactiveRefreshRate.set(refreshRate); - }); - updateRefreshRateController(); -} - - -int RefreshRateManager::getInteractiveRefreshRate() const { - return _refreshRateLock.resultWithReadLock([&] { - return _interactiveRefreshRate.get(); - }); -} diff --git a/interface/src/RefreshRateManager.h b/interface/src/RefreshRateManager.h index ee7debe3ef..6ded8c8869 100644 --- a/interface/src/RefreshRateManager.h +++ b/interface/src/RefreshRateManager.h @@ -15,6 +15,8 @@ #include #include +#include + #include #include @@ -28,7 +30,8 @@ public: }; enum RefreshRateRegime { - RUNNING = 0, + FOCUS_ACTIVE = 0, + FOCUS_INACTIVE, UNFOCUS, MINIMIZED, STARTUP, @@ -38,7 +41,7 @@ public: enum UXMode { DESKTOP = 0, - HMD, + VR, UX_NUM }; @@ -57,8 +60,9 @@ public: void setRefreshRateOperator(std::function refreshRateOperator) { _refreshRateOperator = refreshRateOperator; } int getActiveRefreshRate() const { return _activeRefreshRate; } void updateRefreshRateController() const; - void setInteractiveRefreshRate(int refreshRate); - int getInteractiveRefreshRate() const; + + void resetInactiveTimer(); + void toggleInactive(); static std::string refreshRateProfileToString(RefreshRateProfile refreshRateProfile); static RefreshRateProfile refreshRateProfileFromString(std::string refreshRateProfile); @@ -66,18 +70,17 @@ public: static std::string refreshRateRegimeToString(RefreshRateRegime refreshRateRegime); private: - mutable ReadWriteLockable _refreshRateLock; - mutable ReadWriteLockable _refreshRateModeLock; - mutable int _activeRefreshRate { 20 }; RefreshRateProfile _refreshRateProfile { RefreshRateProfile::INTERACTIVE}; RefreshRateRegime _refreshRateRegime { RefreshRateRegime::STARTUP }; UXMode _uxMode; - Setting::Handle _interactiveRefreshRate { "interactiveRefreshRate", 20}; - Setting::Handle _refreshRateMode { "refreshRateProfile", INTERACTIVE }; + mutable ReadWriteLockable _refreshRateProfileSettingLock; + Setting::Handle _refreshRateProfileSetting { "refreshRateProfile", RefreshRateProfile::INTERACTIVE }; std::function _refreshRateOperator { nullptr }; + + std::shared_ptr _inactiveTimer { std::make_shared() }; }; #endif diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 2025d3cabc..00e743312f 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -46,6 +46,7 @@ #include "MyAvatar.h" #include "DebugDraw.h" #include "SceneScriptingInterface.h" +#include "ui/AvatarCertifyBanner.h" // 50 times per second - target is 45hz, but this helps account for any small deviations // in the update loop - this also results in ~30hz when in desktop mode which is essentially @@ -178,6 +179,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) { _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } + + static AvatarCertifyBanner theftBanner; + if (_myAvatar->isCertifyFailed()) { + theftBanner.show(_myAvatar->getSessionUUID()); + } else { + theftBanner.clear(); + } } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 42f2205861..9e72d9ea15 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -18,7 +18,7 @@ #include /**jsdoc - * The Clipboard API enables you to export and import entities to and from JSON files. + * The Clipboard API enables you to export and import entities to and from JSON files. * * @namespace Clipboard * @@ -33,56 +33,92 @@ public: public: /**jsdoc - * Compute the extents of the contents held in the clipboard. + * Gets the extents of the entities held in the clipboard. * @function Clipboard.getContentsDimensions - * @returns {Vec3} The extents of the contents held in the clipboard. + * @returns {Vec3} The extents of the content held in the clipboard. + * @example Import entities to the clipboard and report their overall dimensions. + * var filename = Window.browse("Import entities to clipboard", "", "*.json"); + * if (filename) { + * if (Clipboard.importEntities(filename)) { + * print("Clipboard dimensions: " + JSON.stringify(Clipboard.getContentsDimensions())); + * } + * } */ Q_INVOKABLE glm::vec3 getContentsDimensions(); /**jsdoc - * Compute the largest dimension of the extents of the contents held in the clipboard. + * Gets the largest dimension of the extents of the entities held in the clipboard. * @function Clipboard.getClipboardContentsLargestDimension - * @returns {number} The largest dimension computed. + * @returns {number} The largest dimension of the extents of the content held in the clipboard. */ Q_INVOKABLE float getClipboardContentsLargestDimension(); /**jsdoc - * Import entities from a JSON file containing entity data into the clipboard. - * You can generate a JSON file using {@link Clipboard.exportEntities}. + * Imports entities from a JSON file into the clipboard. * @function Clipboard.importEntities - * @param {string} filename Path and name of file to import. - * @param {boolean} does the ResourceRequestObserver observe this request? - * @param {number} optional internal id of object causing this import. + * @param {string} filename - The path and name of the JSON file to import. + * @param {boolean} [isObservable=true] - true if the {@link ResourceRequestObserver} can observe this + * request, false if it can't. + * @param {number} [callerID=-1] - An integer ID that is passed through to the {@link ResourceRequestObserver}. * @returns {boolean} true if the import was successful, otherwise false. + * @example Import entities and paste into the domain. + * var filename = Window.browse("Import entities to clipboard", "", "*.json"); + * if (filename) { + * if (Clipboard.importEntities(filename)) { + * pastedEntities = Clipboard.pasteEntities(Vec3.sum(MyAvatar.position, + * Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 }))); + * print("Entities pasted: " + JSON.stringify(pastedEntities)); + * } + * } */ Q_INVOKABLE bool importEntities(const QString& filename, const bool isObservable = true, const qint64 callerId = -1); /**jsdoc - * Export the entities specified to a JSON file. + * Exports specified entities to a JSON file. * @function Clipboard.exportEntities - * @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json". - * @param {Uuid[]} entityIDs Array of IDs of the entities to export. - * @returns {boolean} true if the export was successful, otherwise false. + * @param {string} filename - Path and name of the file to export the entities to. Should have the extension ".json". + * @param {Uuid[]} entityIDs - The IDs of the entities to export. + * @returns {boolean} true if entities were found and the file was written, otherwise false. + * @example Create and export a cube and a sphere. + * // Create entities. + * var box = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.2, y: 0, z: -3 })), + * lifetime: 300 // Delete after 5 minutes. + * }); + * var sphere = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.2, y: 0, z: -3 })), + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Export entities. + * var filename = Window.save("Export entities to JSON file", Paths.resources, "*.json"); + * if (filename) { + * Clipboard.exportEntities(filename, [box, sphere]); + * } */ Q_INVOKABLE bool exportEntities(const QString& filename, const QVector& entityIDs); /**jsdoc - * Export the entities with centers within a cube to a JSON file. + * Exports all entities that have centers within a cube to a JSON file. * @function Clipboard.exportEntities - * @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json". - * @param {number} x X-coordinate of the cube center. - * @param {number} y Y-coordinate of the cube center. - * @param {number} z Z-coordinate of the cube center. - * @param {number} scale Half dimension of the cube. - * @returns {boolean} true if the export was successful, otherwise false. + * @variation 0 + * @param {string} filename - Path and name of the file to export the entities to. Should have the extension ".json". + * @param {number} x - X-coordinate of the cube center. + * @param {number} y - Y-coordinate of the cube center. + * @param {number} z - Z-coordinate of the cube center. + * @param {number} scale - Half dimension of the cube. + * @returns {boolean} true if entities were found and the file was written, otherwise false. */ Q_INVOKABLE bool exportEntities(const QString& filename, float x, float y, float z, float scale); /**jsdoc - * Paste the contents of the clipboard into the world. + * Pastes the contents of the clipboard into the domain. * @function Clipboard.pasteEntities - * @param {Vec3} position Position to paste the clipboard contents at. - * @returns {Uuid[]} Array of entity IDs for the new entities that were created as a result of the paste operation. + * @param {Vec3} position - The position to paste the clipboard contents at. + * @returns {Uuid[]} The IDs of the new entities that were created as a result of the paste operation. If entities couldn't + * be created then an empty array is returned. */ Q_INVOKABLE QVector pasteEntities(glm::vec3 position); }; diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 9701c911b8..4fb631463e 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -26,18 +26,20 @@ class ScriptEngine; /**jsdoc - * The Controller API provides facilities to interact with computer and controller hardware. + * The Controller API provides facilities to interact with computer and controller hardware. * - *
Functions
+ *

Facilities

* - *

Properties

+ *

Properties

+ *

Get Controller property trees.

*
    *
  • {@link Controller.getActions|getActions}
  • *
  • {@link Controller.getHardware|getHardware}
  • *
  • {@link Controller.getStandard|getStandard}
  • *
* - *

Mappings

+ *

Mappings

+ *

Create and enable or disable Controller mappings.

*
    *
  • {@link Controller.disableMapping|disableMapping}
  • *
  • {@link Controller.enableMapping|enableMapping}
  • @@ -46,7 +48,8 @@ class ScriptEngine; *
  • {@link Controller.parseMapping|parseMapping}
  • *
* - *

Input, Hardware, and Action Reflection

+ *

Input, Hardware, and Action Reflection

+ *

Information on the devices and actions available.

*
    *
  • {@link Controller.findAction|findAction}
  • *
  • {@link Controller.findDevice|findDevice}
  • @@ -55,16 +58,20 @@ class ScriptEngine; *
  • {@link Controller.getAvailableInputs|getAvailableInputs}
  • *
  • {@link Controller.getDeviceName|getDeviceName}
  • *
  • {@link Controller.getDeviceNames|getDeviceNames}
  • + *
  • {@link Controller.getRunningInputDevices|getRunningInputDevices}
  • *
* - *

Input, Hardware, and Action Events

+ *

Input, Hardware, and Action Signals

+ *

Notifications of device and action events.

*
    *
  • {@link Controller.actionEvent|actionEvent}
  • *
  • {@link Controller.hardwareChanged|hardwareChanged}
  • + *
  • {@link Controller.inputDeviceRunningChanged|inputDeviceRunningChanged}
  • *
  • {@link Controller.inputEvent|inputEvent}
  • *
* - *

Mouse, Keyboard, and Touch Events

+ *

Mouse, Keyboard, and Touch Signals

+ *

Notifications of mouse, keyboard, and touch events.

*
    *
  • {@link Controller.keyPressEvent|keyPressEvent}
  • *
  • {@link Controller.keyReleaseEvent|keyReleaseEvent}
  • @@ -78,29 +85,32 @@ class ScriptEngine; *
  • {@link Controller.wheelEvent|wheelEvent}
  • *
* - *

Control Capturing

+ *

Control Capturing

+ *

Disable and enable the processing of mouse and touch events.

*
    *
  • {@link Controller.captureMouseEvents|captureMouseEvents}
  • - *
  • {@link Controller.captureTouchEvents|captureTouchEvents}
  • *
  • {@link Controller.captureWheelEvents|captureWheelEvents}
  • + *
  • {@link Controller.captureTouchEvents|captureTouchEvents}
  • *
  • {@link Controller.releaseMouseEvents|releaseMouseEvents}
  • - *
  • {@link Controller.releaseTouchEvents|releaseTouchEvents}
  • *
  • {@link Controller.releaseWheelEvents|releaseWheelEvents}
  • + *
  • {@link Controller.releaseTouchEvents|releaseTouchEvents}
  • *
* - *

Action Capturing

+ *

Action Capturing

+ *

Disable and enable controller actions.

*
    *
  • {@link Controller.captureActionEvents|captureActionEvents}
  • - *
  • {@link Controller.captureEntityClickEvents|captureEntityClickEvents}
  • - *
  • {@link Controller.captureJoystick|captureJoystick}
  • *
  • {@link Controller.captureKeyEvents|captureKeyEvents}
  • + *
  • {@link Controller.captureJoystick|captureJoystick}
  • + *
  • {@link Controller.captureEntityClickEvents|captureEntityClickEvents}
  • *
  • {@link Controller.releaseActionEvents|releaseActionEvents}
  • - *
  • {@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}
  • - *
  • {@link Controller.releaseJoystick|releaseJoystick}
  • *
  • {@link Controller.releaseKeyEvents|releaseKeyEvents}
  • + *
  • {@link Controller.releaseJoystick|releaseJoystick}
  • + *
  • {@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}
  • *
* - *

Controller and Action Values

+ *

Controller and Action Values

+ *

Get the current value of controller outputs and actions.

*
    *
  • {@link Controller.getValue|getValue}
  • *
  • {@link Controller.getAxisValue|getAxisValue}
  • @@ -108,7 +118,8 @@ class ScriptEngine; *
  • {@link Controller.getActionValue|getActionValue}
  • *
* - *

Haptics

+ *

Haptics

+ *

Trigger haptic pulses.

*
    *
  • {@link Controller.triggerHapticPulse|triggerHapticPulse}
  • *
  • {@link Controller.triggerHapticPulseOnDevice|triggerHapticPulseOnDevice}
  • @@ -116,20 +127,23 @@ class ScriptEngine; *
  • {@link Controller.triggerShortHapticPulseOnDevice|triggerShortHapticPulseOnDevice}
  • *
* - *

Display Information

+ *

Display Information

+ *

Get information on the display.

*
    *
  • {@link Controller.getViewportDimensions|getViewportDimensions}
  • *
  • {@link Controller.getRecommendedHUDRect|getRecommendedHUDRect}
  • *
* - *

Virtual Game Pad

+ *

Virtual Game Pad

+ *

Use the virtual game pad which is available on some devices.

*
    *
  • {@link Controller.setVPadEnabled|setVPadEnabled}
  • *
  • {@link Controller.setVPadHidden|setVPadHidden}
  • *
  • {@link Controller.setVPadExtraBottomMargin|setVPadExtraBottomMargin}
  • *
* - *

Input Recordings

+ *

Input Recordings

+ *

Create and play input recordings.

*
    *
  • {@link Controller.startInputRecording|startInputRecording}
  • *
  • {@link Controller.stopInputRecording|stopInputRecording}
  • @@ -140,10 +154,10 @@ class ScriptEngine; *
  • {@link Controller.stopInputPlayback|stopInputPlayback}
  • *
* - *
Entity Methods:
+ *

Entity Methods

* *

The default scripts implement hand controller actions that use {@link Entities.callEntityMethod} to call entity script - * methods, if present in the entity being interacted with.

+ * methods, if present, in the entity being interacted with.

* * * @@ -203,7 +217,7 @@ class ScriptEngine; * * @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end * points in a {@link RouteObject} mapping. A synonym for Controller.Hardware.Actions. - * Read-only.
+ * Read-only.

* Default mappings are provided from the Controller.Hardware.Keyboard and Controller.Standard to * actions in * @@ -217,7 +231,7 @@ class ScriptEngine; * controller outputs. Read-only. * * @property {Controller.Standard} Standard - Standard controller outputs that can be mapped to Actions or - * functions in a {@link RouteObject} mapping. Read-only.
+ * functions in a {@link RouteObject} mapping. Read-only.

* Each hardware device has a mapping from its outputs to Controller.Standard items, specified in a JSON file. * For example,
* leapmotion.json and @@ -253,7 +267,7 @@ public: public slots: /**jsdoc - * Disable default Interface actions for a particular key event. + * Disables default Interface actions for a particular key event. * @function Controller.captureKeyEvents * @param {KeyEvent} event - Details of the key event to be captured. The key property must be specified. The * text property is ignored. The other properties default to false. @@ -272,7 +286,7 @@ public slots: virtual void captureKeyEvents(const KeyEvent& event); /**jsdoc - * Re-enable default Interface actions for a particular key event that has been disabled using + * Re-enables default Interface actions for a particular key event that has been disabled using * {@link Controller.captureKeyEvents|captureKeyEvents}. * @function Controller.releaseKeyEvents * @param {KeyEvent} event - Details of the key event to release from capture. The key property must be @@ -281,7 +295,7 @@ public slots: virtual void releaseKeyEvents(const KeyEvent& event); /**jsdoc - * Disable default Interface actions for a joystick. + * Disables default Interface actions for a joystick. * @function Controller.captureJoystick * @param {number} joystickID - The integer ID of the joystick. * @deprecated This function is deprecated and will be removed. It no longer has any effect. @@ -289,7 +303,7 @@ public slots: virtual void captureJoystick(int joystickIndex); /**jsdoc - * Re-enable default Interface actions for a joystick that has been disabled using + * Re-enables default Interface actions for a joystick that has been disabled using * {@link Controller.captureJoystick|captureJoystick}. * @function Controller.releaseJoystick * @param {number} joystickID - The integer ID of the joystick. @@ -298,7 +312,7 @@ public slots: virtual void releaseJoystick(int joystickIndex); /**jsdoc - * Disable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities. + * Disables {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities. * @function Controller.captureEntityClickEvents * @example * Entities.mousePressOnEntity.connect(function (entityID, event) { @@ -316,7 +330,7 @@ public slots: virtual void captureEntityClickEvents(); /**jsdoc - * Re-enable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were + * Re-enables {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were * disabled using {@link Controller.captureEntityClickEvents|captureEntityClickEvents}. * @function Controller.releaseEntityClickEvents */ @@ -324,14 +338,14 @@ public slots: /**jsdoc - * Get the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode. + * Gets the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode. * @function Controller.getViewportDimensions * @returns {Vec2} The dimensions of the Interface window interior if in desktop mode or HUD surface if in HMD mode. */ virtual glm::vec2 getViewportDimensions() const; /**jsdoc - * Get the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop + * Gets the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop * mode. * @function Controller.getRecommendedHUDRect * @returns {Rect} The recommended area in which to position UI. diff --git a/interface/src/ui/AvatarCertifyBanner.cpp b/interface/src/ui/AvatarCertifyBanner.cpp new file mode 100644 index 0000000000..3ffae43c5b --- /dev/null +++ b/interface/src/ui/AvatarCertifyBanner.cpp @@ -0,0 +1,76 @@ +// +// AvatarCertifyBanner.h +// interface/src/ui +// +// Created by Simon Walton, April 2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AvatarCertifyBanner.h" + +#include + +#include "ui/TabletScriptingInterface.h" +#include "EntityTreeRenderer.h" + +namespace { + const QUrl AVATAR_THEFT_BANNER_IMAGE = PathUtils::resourcesUrl("images/AvatarTheftBanner.png"); + const QString AVATAR_THEFT_BANNER_SCRIPT { "/system/clickToAvatarApp.js" }; +} + +AvatarCertifyBanner::AvatarCertifyBanner(QQuickItem* parent) { +} + +void AvatarCertifyBanner::show(const QUuid& avatarID) { + if (!_active) { + auto entityTreeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = entityTreeRenderer->getTree(); + if (!entityTree) { + return; + } + const bool tabletShown = DependencyManager::get()->property("tabletShown").toBool(); + const auto& position = tabletShown ? glm::vec3(0.0f, 0.0f, -1.8f) : glm::vec3(0.0f, 0.0f, -0.7f); + const float scaleFactor = tabletShown ? 2.6f : 1.0f; + + EntityItemProperties entityProperties; + entityProperties.setType(EntityTypes::Image); + entityProperties.setEntityHostType(entity::HostType::LOCAL); + entityProperties.setImageURL(AVATAR_THEFT_BANNER_IMAGE.toString()); + entityProperties.setName("hifi-avatar-notification-banner"); + entityProperties.setParentID(avatarID); + entityProperties.setParentJointIndex(CAMERA_MATRIX_INDEX); + entityProperties.setLocalPosition(position); + entityProperties.setDimensions(glm::vec3(1.0f, 1.0f, 0.3f) * scaleFactor); + entityProperties.setRenderLayer(tabletShown ? RenderLayer::WORLD : RenderLayer::FRONT); + entityProperties.getGrab().setGrabbable(false); + QString scriptPath = QUrl(PathUtils::defaultScriptsLocation("")).toString() + AVATAR_THEFT_BANNER_SCRIPT; + entityProperties.setScript(scriptPath); + entityProperties.setVisible(true); + + entityTree->withWriteLock([&] { + auto entityTreeItem = entityTree->addEntity(_bannerID, entityProperties); + entityTreeItem->setLocalPosition(position); + }); + + _active = true; + } +} + +void AvatarCertifyBanner::clear() { + if (_active) { + auto entityTreeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = entityTreeRenderer->getTree(); + if (!entityTree) { + return; + } + + entityTree->withWriteLock([&] { + entityTree->deleteEntity(_bannerID); + }); + + _active = false; + } +} diff --git a/interface/src/ui/AvatarCertifyBanner.h b/interface/src/ui/AvatarCertifyBanner.h new file mode 100644 index 0000000000..c9bb23cb96 --- /dev/null +++ b/interface/src/ui/AvatarCertifyBanner.h @@ -0,0 +1,34 @@ +// +// AvatarCertifyBanner.h +// interface/src/ui +// +// Created by Simon Walton, April 2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AvatarCertifyBanner_h +#define hifi_AvatarCertifyBanner_h + +#include +#include "OffscreenQmlElement.h" +#include "EntityItemID.h" + +class EntityItemID; + +class AvatarCertifyBanner : QObject { + Q_OBJECT + HIFI_QML_DECL +public: + AvatarCertifyBanner(QQuickItem* parent = nullptr); + void show(const QUuid& avatarID); + void clear(); + +private: + const EntityItemID _bannerID { QUuid::createUuid() }; + bool _active { false }; +}; + +#endif // hifi_AvatarCertifyBanner_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 204ed79660..cb0acd68cb 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1428,7 +1428,7 @@ int Avatar::getJointIndex(const QString& name) const { withValidJointIndicesCache([&]() { if (_modelJointIndicesCache.contains(name)) { - result = _modelJointIndicesCache[name] - 1; + result = _modelJointIndicesCache.value(name) - 1; } }); return result; @@ -1439,9 +1439,7 @@ QStringList Avatar::getJointNames() const { withValidJointIndicesCache([&]() { // find out how large the vector needs to be int maxJointIndex = -1; - QHashIterator k(_modelJointIndicesCache); - while (k.hasNext()) { - k.next(); + for (auto k = _modelJointIndicesCache.constBegin(); k != _modelJointIndicesCache.constEnd(); k++) { int index = k.value(); if (index > maxJointIndex) { maxJointIndex = index; @@ -1450,9 +1448,7 @@ QStringList Avatar::getJointNames() const { // iterate through the hash and put joint names // into the vector at their indices QVector resultVector(maxJointIndex+1); - QHashIterator i(_modelJointIndicesCache); - while (i.hasNext()) { - i.next(); + for (auto i = _modelJointIndicesCache.constBegin(); i != _modelJointIndicesCache.constEnd(); i++) { int index = i.value(); resultVector[index] = i.key(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 295a0e9f52..d8f24208b0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -164,7 +164,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { Parent::simulate(deltaTime, fullUpdate); } - // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem and ModelOverlay, + // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem, // but Avatars don't get updates in the same way if (!_texturesLoaded && getGeometry() && getGeometry()->areTexturesLoaded()) { _texturesLoaded = true; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index aea214efd7..942b13c237 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1955,8 +1955,7 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity >> identity.attachmentData >> identity.displayName >> identity.sessionDisplayName - >> identity.isReplicated - >> identity.lookAtSnappingEnabled + >> identity.identityFlags ; if (incomingSequenceNumber > _identitySequenceNumber) { @@ -1971,8 +1970,22 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity } maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); - if (identity.isReplicated != _isReplicated) { - _isReplicated = identity.isReplicated; + bool flagValue; + flagValue = identity.identityFlags.testFlag(AvatarDataPacket::IdentityFlag::isReplicated); + if ( flagValue != _isReplicated) { + _isReplicated = flagValue; + identityChanged = true; + } + + flagValue = identity.identityFlags.testFlag(AvatarDataPacket::IdentityFlag::lookAtSnapping); + if ( flagValue != _lookAtSnappingEnabled) { + setProperty("lookAtSnappingEnabled", flagValue); + identityChanged = true; + } + + flagValue = identity.identityFlags.testFlag(AvatarDataPacket::IdentityFlag::verificationFailed); + if (flagValue != _verificationFailed) { + _verificationFailed = flagValue; identityChanged = true; } @@ -1981,11 +1994,6 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity identityChanged = true; } - if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) { - setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled); - identityChanged = true; - } - #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ << "identity.uuid:" << identity.uuid @@ -2195,17 +2203,27 @@ void AvatarData::prepareResetTraitInstances() { QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); + using namespace AvatarDataPacket; // when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received // whereas agents send a fresh outgoing sequence number when identity data has changed + IdentityFlags identityFlags = IdentityFlag::none; + if (_isReplicated || setIsReplicated) { + identityFlags.setFlag(IdentityFlag::isReplicated); + } + if (_lookAtSnappingEnabled) { + identityFlags.setFlag(IdentityFlag::lookAtSnapping); + } + if (isCertifyFailed()) { + identityFlags.setFlag(IdentityFlag::verificationFailed); + } identityStream << getSessionUUID() << (udt::SequenceNumber::Type) _identitySequenceNumber << _attachmentData << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName - << (_isReplicated || setIsReplicated) - << _lookAtSnappingEnabled; + << identityFlags; return identityData; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 76fa9e0a34..9219c2c03f 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -378,6 +378,10 @@ namespace AvatarDataPacket { static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; + // AvatarIdentity packet: + enum class IdentityFlag: quint32 {none, isReplicated = 0x1, lookAtSnapping = 0x2, verificationFailed = 0x4}; + Q_DECLARE_FLAGS(IdentityFlags, IdentityFlag) + struct SendStatus { HasFlags itemFlags { 0 }; bool sendUUID { false }; @@ -1182,6 +1186,7 @@ public: QString sessionDisplayName; bool isReplicated; bool lookAtSnappingEnabled; + AvatarDataPacket::IdentityFlags identityFlags; }; // identityChanged returns true if identity has changed, false otherwise. @@ -1213,6 +1218,7 @@ public: _sessionDisplayName = sessionDisplayName; markIdentityDataChanged(); } + virtual bool isCertifyFailed() const { return _verificationFailed; } /**jsdoc * Gets information about the models currently attached to your avatar. @@ -1694,6 +1700,7 @@ protected: QString _displayName; QString _sessionDisplayName { }; bool _lookAtSnappingEnabled { true }; + bool _verificationFailed { false }; quint64 _errorLogExpiry; ///< time in future when to log an error diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 29a40c5b6b..cb14d7ef41 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -23,6 +23,8 @@ #include "Profile.h" +static const QString VERIFY_FAIL_MODEL { "/meshes/verifyFailed.fst" }; + void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { if (parentID == QUuid()) { return; @@ -324,6 +326,10 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer bool displayNameChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); + if (avatar->isCertifyFailed() && identityUUID != EMPTY) { + qCDebug(avatars) << "Avatar" << avatar->getSessionDisplayName() << "marked as VERIFY-FAILED"; + avatar->setSkeletonModelURL(PathUtils::resourcesUrl(VERIFY_FAIL_MODEL)); + } _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); } } diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index e34ce277e2..9f9d92fed7 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -35,9 +35,9 @@ namespace controller { /**jsdoc *

The Controller.Actions object has properties representing predefined actions on the user's avatar and - * Interface. The property values are integer IDs, uniquely identifying each action. Read-only. These can be used - * as end points in the routes of a {@link MappingObject}. The data routed to each action is either a number or a - * {@link Pose}.

+ * Interface. The property values are integer IDs, uniquely identifying each action. Read-only.

+ *

These actions can be used as end points in the routes of a {@link MappingObject}. The data item routed to each action + * is either a number or a {@link Pose}.

* *
Disable entity click events for a short period.
* @@ -178,7 +178,7 @@ namespace controller { * person view. * - * + * * * * @@ -238,71 +238,49 @@ namespace controller { * * + * action is deprecated and will be removed. Use RightHand instead. * + * action is deprecated and will be removed. Use BoomIn instead. * + * action is deprecated and will be removed. Use BoomOut instead. * + * action is deprecated and will be removed. Use ContextMenu instead. * + * action is deprecated and will be removed. Use ToggleMute instead. * + * action is deprecated and will be removed. Use TogglePushToTalk instead. * + * action is deprecated and will be removed. Use Sprint instead. * + * action is deprecated and will be removed. Use Backward instead. * + * action is deprecated and will be removed. Use Forward instead. * + * action is deprecated and will be removed. Use StrafeLeft instead. * + * action is deprecated and will be removed. Use StrafeRight instead. * + * action is deprecated and will be removed. Use Up instead. * + * action is deprecated and will be removed. Use Down instead. * + * action is deprecated and will be removed. Use PitchDown instead. * + * action is deprecated and will be removed. Use PitchUp instead. * + * action is deprecated and will be removed. Use YawLeft instead. * + * action is deprecated and will be removed. Use YawRight instead. * + * action is deprecated and will be removed. Use LeftHandClick instead. * + * action is deprecated and will be removed. Use RightHandClick instead. * + * action is deprecated and will be removed. Use Shift instead. * + * action is deprecated and will be removed. Use PrimaryAction instead. * + * action is deprecated and will be removed. Use SecondaryAction instead. * * * @@ -143,7 +143,7 @@ namespace controller { Q_INVOKABLE QVector getDeviceNames(); /**jsdoc - * Find the ID of an action from its name. + * Finds the ID of an action from its name. * @function Controller.findAction * @param {string} actionName - The name of the action: one of the {@link Controller.Actions} property names. * @returns {number} The integer ID of the action if found, otherwise 4095. Note that this value is not @@ -156,7 +156,7 @@ namespace controller { Q_INVOKABLE int findAction(QString actionName); /**jsdoc - * Get the names of all actions available as properties of {@link Controller.Actions}. + * Gets the names of all actions available as properties of {@link Controller.Actions}. * @function Controller.getActionNames * @returns {string[]} An array of action names. * @example @@ -167,7 +167,7 @@ namespace controller { Q_INVOKABLE QVector getActionNames() const; /**jsdoc - * Get the value of a controller button or axis output. Note: Also gets the value of a controller axis output. + * Gets the value of a controller button or axis output. Note: Also gets the value of a controller axis output. * @function Controller.getValue * @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} item. * @returns {number} The current value of the controller item output if source is valid, otherwise @@ -186,7 +186,7 @@ namespace controller { Q_INVOKABLE float getValue(const int& source) const; /**jsdoc - * Get the value of a controller axis output. Note: Also gets the value of a controller button output. + * Gets the value of a controller axis output. Note: Also gets the value of a controller button output. * @function Controller.getAxisValue * @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} item. * @returns {number} The current value of the controller item output if source is valid, otherwise @@ -196,7 +196,7 @@ namespace controller { Q_INVOKABLE float getAxisValue(int source) const; /**jsdoc - * Get the value of a controller pose output. + * Gets the value of a controller pose output. * @function Controller.getPoseValue * @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} pose output. * @returns {Pose} The current value of the controller pose output if source is a pose output, otherwise @@ -210,9 +210,9 @@ namespace controller { /**jsdoc * Triggers a haptic pulse on connected and enabled devices that have the capability. * @function Controller.triggerHapticPulse - * @param {number} strength - The strength of the haptic pulse, 0.01.0. + * @param {number} strength - The strength of the haptic pulse, range 0.01.0. * @param {number} duration - The duration of the haptic pulse, in milliseconds. - * @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on. + * @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on. * @example * var HAPTIC_STRENGTH = 0.5; * var HAPTIC_DURATION = 10; @@ -224,8 +224,8 @@ namespace controller { /**jsdoc * Triggers a 250ms haptic pulse on connected and enabled devices that have the capability. * @function Controller.triggerShortHapticPulse - * @param {number} strength - The strength of the haptic pulse, 0.01.0. - * @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on. + * @param {number} strength - The strength of the haptic pulse, range 0.01.0. + * @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on. */ Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const; @@ -233,9 +233,9 @@ namespace controller { * Triggers a haptic pulse on a particular device if connected and enabled and it has the capability. * @function Controller.triggerHapticPulseOnDevice * @param {number} deviceID - The ID of the device to trigger the haptic pulse on. - * @param {number} strength - The strength of the haptic pulse, 0.01.0. + * @param {number} strength - The strength of the haptic pulse, range 0.01.0. * @param {number} duration - The duration of the haptic pulse, in milliseconds. - * @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on. + * @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on. * @example * var HAPTIC_STRENGTH = 0.5; * var deviceID = Controller.findDevice("OculusTouch"); @@ -250,19 +250,19 @@ namespace controller { * Triggers a 250ms haptic pulse on a particular device if connected and enabled and it has the capability. * @function Controller.triggerShortHapticPulseOnDevice * @param {number} deviceID - The ID of the device to trigger the haptic pulse on. - * @param {number} strength - The strength of the haptic pulse, 0.01.0. - * @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on. + * @param {number} strength - The strength of the haptic pulse, range 0.01.0. + * @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on. */ Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH) const; /**jsdoc - * Create a new controller mapping. Routes can then be added to the mapping using {@link MappingObject} methods and + * Creates a new controller mapping. Routes can then be added to the mapping using {@link MappingObject} methods and * routed to Standard controls, Actions, or script functions using {@link RouteObject} * methods. The mapping can then be enabled using {@link Controller.enableMapping|enableMapping} for it to take effect. * @function Controller.newMapping - * @param {string} mappingName=Uuid.generate() - A unique name for the mapping. If not specified a new UUID generated + * @param {string} [mappingName=Uuid.generate()] - A unique name for the mapping. If not specified a new UUID generated * by {@link Uuid.generate} is used. * @returns {MappingObject} A controller mapping object. * @example @@ -279,22 +279,22 @@ namespace controller { Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString()); /**jsdoc - * Enable or disable a controller mapping. When enabled, the routes in the mapping have effect. + * Enables or disables a controller mapping. When enabled, the routes in the mapping have effect. * @function Controller.enableMapping * @param {string} mappingName - The name of the mapping. - * @param {boolean} enable=true - If true then the mapping is enabled, otherwise it is disabled. + * @param {boolean} [[enable=true] - If true then the mapping is enabled, otherwise it is disabled. */ Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); /**jsdoc - * Disable a controller mapping. When disabled, the routes in the mapping have no effect. + * Disables a controller mapping. When disabled, the routes in the mapping have no effect. * @function Controller.disableMapping * @param {string} mappingName - The name of the mapping. */ Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); } /**jsdoc - * Create a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} string. Use + * Creates a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} string. Use * {@link Controller.enableMapping|enableMapping} to enable the mapping for it to take effect. * @function Controller.parseMapping * @param {string} jsonString - A JSON string of the {@link Controller.MappingJSON|MappingJSON}. @@ -317,19 +317,19 @@ namespace controller { Q_INVOKABLE QObject* parseMapping(const QString& json); /**jsdoc - * Create a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} JSON file at a URL. Use + * Creates a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} JSON file at a URL. Use * {@link Controller.enableMapping|enableMapping} to enable the mapping for it to take effect. + *

Warning: This function is not yet implemented; it doesn't load a mapping and just returns + * null. * @function Controller.loadMapping * @param {string} jsonURL - The URL the {@link Controller.MappingJSON|MappingJSON} JSON file. * @returns {MappingObject} A controller mapping object. - * @todo Implement this function. It currently does not load the mapping from the file; it just returns - * null. */ Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl); /**jsdoc - * Get the {@link Controller.Hardware} property tree. Calling this function is the same as using the {@link Controller} + * Gets the {@link Controller.Hardware} property tree. Calling this function is the same as using the {@link Controller} * property, Controller.Hardware. * @function Controller.getHardware * @returns {Controller.Hardware} The {@link Controller.Hardware} property tree. @@ -337,7 +337,7 @@ namespace controller { Q_INVOKABLE const QVariantMap getHardware() { return _hardware; } /**jsdoc - * Get the {@link Controller.Actions} property tree. Calling this function is the same as using the {@link Controller} + * Gets the {@link Controller.Actions} property tree. Calling this function is the same as using the {@link Controller} * property, Controller.Actions. * @function Controller.getActions * @returns {Controller.Actions} The {@link Controller.Actions} property tree. @@ -345,7 +345,7 @@ namespace controller { Q_INVOKABLE const QVariantMap getActions() { return _actions; } //undefined /**jsdoc - * Get the {@link Controller.Standard} property tree. Calling this function is the same as using the {@link Controller} + * Gets the {@link Controller.Standard} property tree. Calling this function is the same as using the {@link Controller} * property, Controller.Standard. * @function Controller.getStandard * @returns {Controller.Standard} The {@link Controller.Standard} property tree. @@ -354,7 +354,7 @@ namespace controller { /**jsdoc - * Start making a recording of currently active controllers. + * Starts making a recording of currently active controllers. * @function Controller.startInputRecording * @example

* // Delay start of recording for 2s. @@ -374,13 +374,13 @@ namespace controller { Q_INVOKABLE void startInputRecording(); /**jsdoc - * Stop making a recording started by {@link Controller.startInputRecording|startInputRecording}. + * Stops making a recording started by {@link Controller.startInputRecording|startInputRecording}. * @function Controller.stopInputRecording */ Q_INVOKABLE void stopInputRecording(); /**jsdoc - * Play back the current recording from the beginning. The current recording may have been recorded by + * Plays back the current recording from the beginning. The current recording may have been recorded by * {@link Controller.startInputRecording|startInputRecording} and * {@link Controller.stopInputRecording|stopInputRecording}, or loaded by * {@link Controller.loadInputRecording|loadInputRecording}. Playback repeats in a loop until @@ -403,13 +403,13 @@ namespace controller { Q_INVOKABLE void startInputPlayback(); /**jsdoc - * Stop play back of a recording started by {@link Controller.startInputPlayback|startInputPlayback}. + * Stops play back of a recording started by {@link Controller.startInputPlayback|startInputPlayback}. * @function Controller.stopInputPlayback */ Q_INVOKABLE void stopInputPlayback(); /**jsdoc - * Save the current recording to a file. The current recording may have been recorded by + * Saves the current recording to a file. The current recording may have been recorded by * {@link Controller.startInputRecording|startInputRecording} and * {@link Controller.stopInputRecording|stopInputRecording}, or loaded by * {@link Controller.loadInputRecording|loadInputRecording}. It is saved in the directory returned by @@ -419,24 +419,26 @@ namespace controller { Q_INVOKABLE void saveInputRecording(); /**jsdoc - * Load an input recording, ready for play back. + * Loads an input recording, ready for play back. * @function Controller.loadInputRecording * @param {string} file - The path to the recording file, prefixed by "file:///". */ Q_INVOKABLE void loadInputRecording(const QString& file); /**jsdoc - * Get the directory in which input recordings are saved. + * Gets the directory in which input recordings are saved. * @function Controller.getInputRecorderSaveDirectory * @returns {string} The directory in which input recordings are saved. */ Q_INVOKABLE QString getInputRecorderSaveDirectory(); /**jsdoc - * Get all the active and enabled (running) input devices - * @function Controller.getRunningInputDevices - * @returns {string[]} An array of strings with the names - */ + * Gets the names of all the active and running (enabled) input devices. + * @function Controller.getRunningInputDevices + * @returns {string[]} The list of current active and running input devices. + * @example + * print("Running devices: " + JSON.stringify(Controller.getRunningInputDeviceNames())); + */ Q_INVOKABLE QStringList getRunningInputDeviceNames(); bool isMouseCaptured() const { return _mouseCaptured; } @@ -447,7 +449,7 @@ namespace controller { public slots: /**jsdoc - * Disable processing of mouse "move", "press", "double-press", and "release" events into + * Disables processing of mouse "move", "press", "double-press", and "release" events into * {@link Controller.Hardware|Controller.Hardware.Keyboard} outputs. * @function Controller.captureMouseEvents * @example @@ -475,7 +477,7 @@ namespace controller { virtual void captureMouseEvents() { _mouseCaptured = true; } /**jsdoc - * Enable processing of mouse "move", "press", "double-press", and "release" events into + * Enables processing of mouse "move", "press", "double-press", and "release" events into * {@link Controller.Hardware-Keyboard|Controller.Hardware.Keyboard} outputs that were disabled using * {@link Controller.captureMouseEvents|captureMouseEvents}. * @function Controller.releaseMouseEvents @@ -484,7 +486,7 @@ namespace controller { /**jsdoc - * Disable processing of touch "begin", "update", and "end" events into + * Disables processing of touch "begin", "update", and "end" events into * {@link Controller.Hardware|Controller.Hardware.Keyboard}, * {@link Controller.Hardware|Controller.Hardware.Touchscreen}, and * {@link Controller.Hardware|Controller.Hardware.TouchscreenVirtualPad} outputs. @@ -493,7 +495,7 @@ namespace controller { virtual void captureTouchEvents() { _touchCaptured = true; } /**jsdoc - * Enable processing of touch "begin", "update", and "end" events into + * Enables processing of touch "begin", "update", and "end" events into * {@link Controller.Hardware|Controller.Hardware.Keyboard}, * {@link Controller.Hardware|Controller.Hardware.Touchscreen}, and * {@link Controller.Hardware|Controller.Hardware.TouchscreenVirtualPad} outputs that were disabled using @@ -504,14 +506,14 @@ namespace controller { /**jsdoc - * Disable processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard} + * Disables processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard} * outputs. * @function Controller.captureWheelEvents */ virtual void captureWheelEvents() { _wheelCaptured = true; } /**jsdoc - * Enable processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard} + * Enables processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard} * outputs that wer disabled using {@link Controller.captureWheelEvents|captureWheelEvents}. * @function Controller.releaseWheelEvents */ @@ -519,7 +521,7 @@ namespace controller { /**jsdoc - * Disable translating and rotating the user's avatar in response to keyboard and controller controls. + * Disables translating and rotating the user's avatar in response to keyboard and controller controls. * @function Controller.captureActionEvents * @example * Script.setTimeout(function () { @@ -533,12 +535,19 @@ namespace controller { virtual void captureActionEvents() { _actionsCaptured = true; } /**jsdoc - * Enable translating and rotating the user's avatar in response to keyboard and controller controls that were disabled + * Enables translating and rotating the user's avatar in response to keyboard and controller controls that were disabled * using {@link Controller.captureActionEvents|captureActionEvents}. * @function Controller.releaseActionEvents */ virtual void releaseActionEvents() { _actionsCaptured = false; } + /**jsdoc + * @function Controller.updateRunningInputDevices + * @param {string} deviceName - Device name. + * @param {boolean} isRunning - Is running. + * @param {string[]} runningDevices - Running devices. + * @deprecated This function is deprecated and will be removed. + */ void updateRunningInputDevices(const QString& deviceName, bool isRunning, const QStringList& runningDevices); signals: @@ -593,7 +602,7 @@ namespace controller { /**jsdoc * Triggered when a device is registered or unregistered by a plugin. Not all plugins generate - * hardwareChanged events: for example connecting or disconnecting a mouse will not generate an event but + * hardwareChanged events: for example, connecting or disconnecting a mouse will not generate an event but * connecting or disconnecting an Xbox controller will. * @function Controller.hardwareChanged * @returns {Signal} @@ -601,13 +610,13 @@ namespace controller { void hardwareChanged(); /**jsdoc - * Triggered when a device is enabled/disabled - * Enabling/Disabling Leapmotion on settings/controls will trigger this signal. - * @function Controller.deviceRunningChanged - * @param {string} deviceName - The name of the device that is getting enabled/disabled - * @param {boolean} isEnabled - Return if the device is enabled. - * @returns {Signal} - */ + * Triggered when an input device starts or stops being active and running (enabled). For example, enabling or + * disabling the LeapMotion in Settings > Controls > Calibration will trigger this signal. + * @function Controller.inputDeviceRunningChanged + * @param {string} deviceName - The name of the device. + * @param {boolean} isRunning - true if the device is active and running, false if it isn't. + * @returns {Signal} + */ void inputDeviceRunningChanged(QString deviceName, bool isRunning); diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index e1733d2524..ece10ecca3 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -30,17 +30,16 @@ void StandardController::focusOutEvent() { /**jsdoc *

The Controller.Standard object has properties representing standard controller outputs. Those for physical * controllers are based on the XBox controller, with aliases for PlayStation. The property values are integer IDs, uniquely - * identifying each output. Read-only. These can be mapped to actions or functions in a {@link RouteObject} - * mapping.

- * - *

The data value provided by each control is either a number or a {@link Pose}. Numbers are typically normalized to - * 0.0 or 1.0 for button states, the range 0.0 – 1.0 for unidirectional scales, - * and the range -1.0 – 1.0 for bidirectional scales.

- * - *

Each hardware device has a mapping from its outputs to Controller.Standard items, specified in a JSON file. - * For example, - * leapmotion.json and - * vive.json.

+ * identifying each output. Read-only.

+ *

These outputs can be mapped to actions or functions in a {@link RouteObject} mapping. The data value provided by each + * control is either a number or a {@link Pose}. Numbers are typically normalized to 0.0 or 1.0 for + * button states, the range 0.0 – 1.0 for unidirectional scales, and the range + * -1.01.0 for bidirectional scales.

+ *

Each hardware device has a mapping from its outputs to a subset of Controller.Standard items, specified in a + * JSON file. For example, + * vive.json + * and + * leapmotion.json.

* *
CycleCameranumbernumberCycle the camera view from first person, to * third person, to full screen mirror, then back to first person and repeat.
ContextMenunumbernumberShow / hide the tablet.
ContextMenunumbernumberShow/hide the tablet.
ToggleMutenumbernumberToggle the microphone mute.
TogglePushToTalknumbernumberToggle push to talk.
ToggleOverlaynumbernumberToggle the display of overlays.
LEFT_HANDnumber{@link Pose}Deprecated: This * action is deprecated and will be removed. Use LeftHand instead.
RIGHT_HANDnumber{@link Pose}Deprecated: This - * action is deprecated and will be removed. Use - * RightHand instead.
BOOM_INnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * BoomIn instead.
BOOM_OUTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * BoomOut instead.
CONTEXT_MENUnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * ContextMenu instead.
TOGGLE_MUTEnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * ToggleMute instead.
TOGGLE_PUSHTOTALKnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * TogglePushToTalk instead.
SPRINTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * Sprint instead.
LONGITUDINAL_BACKWARDnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * Backward instead.
LONGITUDINAL_FORWARDnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * Forward instead.
LATERAL_LEFTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * StrafeLeft instead.
LATERAL_RIGHTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * StrafeRight instead.
VERTICAL_UPnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * Up instead.
VERTICAL_DOWNnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * Down instead.
PITCH_DOWNnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * PitchDown instead.
PITCH_UPnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * PitchUp instead.
YAW_LEFTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * YawLeft instead.
YAW_RIGHTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * YawRight instead.
LEFT_HAND_CLICKnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * LeftHandClick instead.
RIGHT_HAND_CLICKnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * RightHandClick instead.
SHIFTnumbernumberDeprecated: This - * action is deprecated and will be removed. Use - * Shift instead.
ACTION1numbernumberDeprecated: This - * action is deprecated and will be removed. Use - * PrimaryAction instead.
ACTION2numbernumberDeprecated: This - * action is deprecated and will be removed. Use - * SecondaryAction instead.
Deprecated Trackers
TrackedObject00number{@link Pose}Deprecated: diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index 7c3c31cb38..95d0524a4a 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -54,10 +54,9 @@ enum Hand { /**jsdoc *

The Controller.Hardware object has properties representing standard and hardware-specific controller and - * computer outputs, plus predefined actions on Interface and the user's avatar. Read-only. The outputs can be mapped - * to actions or functions in a {@link RouteObject} mapping. Additionally, hardware-specific controller outputs can be mapped - * to standard controller outputs. - * + * computer outputs, plus predefined actions on Interface and the user's avatar. Read-only.

+ *

The outputs can be mapped to actions or functions in a {@link RouteObject} mapping. Additionally, hardware-specific + * controller outputs can be mapped to standard controller outputs. *

Controllers typically implement a subset of the {@link Controller.Standard} controls, plus they may implement some extras. * Some common controllers are included in the table. You can see the outputs provided by these and others by * viewing their {@link Controller.MappingJSON|MappingJSON} files at diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index 07c59e1aaa..fd32b2eb43 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -227,6 +227,7 @@ namespace controller { } QObject* ScriptingInterface::loadMapping(const QString& jsonUrl) { + // FIXME: Implement. https://highfidelity.manuscript.com/f/cases/14188/Implement-Controller-loadMappping return nullptr; } diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 156fc1af7c..84396fc8be 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -73,7 +73,7 @@ namespace controller { virtual ~ScriptingInterface() {}; /**jsdoc - * Get a list of all available actions. + * Gets a list of all available actions. * @function Controller.getAllActions * @returns {Action[]} All available actions. * @deprecated This function is deprecated and will be removed. It no longer works. @@ -82,7 +82,7 @@ namespace controller { Q_INVOKABLE QVector getAllActions(); /**jsdoc - * Get a list of all available inputs for a hardware device. + * Gets a list of all available inputs for a hardware device. * @function Controller.getAvailableInputs * @param {number} deviceID - Integer ID of the hardware device. * @returns {NamedPair[]} All available inputs for the device. @@ -92,7 +92,7 @@ namespace controller { Q_INVOKABLE QVector getAvailableInputs(unsigned int device); /**jsdoc - * Find the name of a particular controller from its device ID. + * Finds the name of a particular controller from its device ID. * @function Controller.getDeviceName * @param {number} deviceID - The integer ID of the device. * @returns {string} The name of the device if found, otherwise "unknown". @@ -106,7 +106,7 @@ namespace controller { Q_INVOKABLE QString getDeviceName(unsigned int device); /**jsdoc - * Get the current value of an action. + * Gets the current value of an action. * @function Controller.getActionValue * @param {number} actionID - The integer ID of the action. * @returns {number} The current value of the action. @@ -121,7 +121,7 @@ namespace controller { Q_INVOKABLE float getActionValue(int action); /**jsdoc - * Find the ID of a specific controller from its device name. + * Finds the ID of a specific controller from its device name. * @function Controller.findDevice * @param {string} deviceName - The name of the device to find. * @returns {number} The integer ID of the device if available, otherwise 65535. @@ -132,7 +132,7 @@ namespace controller { Q_INVOKABLE int findDevice(QString name); /**jsdoc - * Get the names of all currently available controller devices plus "Actions", "Application", and "Standard". + * Gets the names of all currently available controller devices plus "Actions", "Application", and "Standard". * @function Controller.getDeviceNames * @returns {string[]} An array of device names. * @example

Get the names of all currently available controller devices.Get the names of all actions.Trigger a haptic pulse on the right hand.Trigger a haptic pulse on an Oculus Touch controller.Create a simple mapping that makes the right trigger move your avatar up.Make a controller recording.List all active and running input devices.Disable Controller.Hardware.Keyboard mouse events for a short period.Disable avatar translation and rotation for a short period.
* @@ -119,12 +118,12 @@ void StandardController::focusOutEvent() { * button. * - * + * * * + * * * * var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping"; @@ -193,7 +195,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* when(const QScriptValue& expression); /**jsdoc - * Filter numeric route values to lie between two values; values outside this range are not passed on through the + * Filters numeric route values to lie between two values; values outside this range are not passed on through the * route. * @function RouteObject#clamp * @param {number} min - The minimum value to pass through. @@ -214,7 +216,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* clamp(float min, float max); /**jsdoc - * Filter numeric route values such that they are rounded to 0 or 1 without output values + * Filters numeric route values such that they are rounded to 0 or 1 without output values * flickering when the input value hovers around 0.5. For example, this enables you to use an analog input * as if it were a toggle. * @function RouteObject#hysteresis @@ -239,7 +241,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* hysteresis(float min, float max); /**jsdoc - * Filter numeric route values to send at a specified interval. + * Filters numeric route values to send at a specified interval. * @function RouteObject#pulse * @param {number} interval - The interval between sending values, in seconds. * @returns {RouteObject} The RouteObject with the filter applied. @@ -258,7 +260,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* pulse(float interval); /**jsdoc - * Filter numeric and {@link Pose} route values to be scaled by a constant amount. + * Filters numeric and {@link Pose} route values to be scaled by a constant amount. * @function RouteObject#scale * @param {number} multiplier - The scale to multiply the value by. * @returns {RouteObject} The RouteObject with the filter applied. @@ -280,7 +282,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* scale(float multiplier); /**jsdoc - * Filter numeric and {@link Pose} route values to have the opposite sign, e.g., 0.5 is changed to + * Filters numeric and {@link Pose} route values to have the opposite sign, e.g., 0.5 is changed to * -0.5. * @function RouteObject#invert * @returns {RouteObject} The RouteObject with the filter applied. @@ -302,7 +304,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* invert(); /**jsdoc - * Filter numeric route values such that they're sent only when the input value is outside a dead-zone. When the input + * Filters numeric route values such that they're sent only when the input value is outside a dead-zone. When the input * passes the dead-zone value, output is sent starting at 0.0 and catching up with the input value. As the * input returns toward the dead-zone value, output values reduce to 0.0 at the dead-zone value. * @function RouteObject#deadZone @@ -324,7 +326,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* deadZone(float min); /**jsdoc - * Filter numeric route values such that they are rounded to -1, 0, or 1. + * Filters numeric route values such that they are rounded to -1, 0, or 1. * For example, this enables you to use an analog input as if it were a toggle or, in the case of a bidirectional axis, * a tri-state switch. * @function RouteObject#constrainToInteger @@ -345,7 +347,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* constrainToInteger(); /**jsdoc - * Filter numeric route values such that they are rounded to 0 or 1. For example, this + * Filters numeric route values such that they are rounded to 0 or 1. For example, this * enables you to use an analog input as if it were a toggle. * @function RouteObject#constrainToPositiveInteger * @returns {RouteObject} The RouteObject with the filter applied. @@ -364,7 +366,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* constrainToPositiveInteger(); /**jsdoc - * Filter {@link Pose} route values to have a pre-translation applied. + * Filters {@link Pose} route values to have a pre-translation applied. * @function RouteObject#translate * @param {Vec3} translate - The pre-translation to add to the pose. * @returns {RouteObject} The RouteObject with the pre-translation applied. @@ -373,7 +375,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* translate(glm::vec3 translate); /**jsdoc - * Filter {@link Pose} route values to have a pre-transform applied. + * Filters {@link Pose} route values to have a pre-transform applied. * @function RouteObject#transform * @param {Mat4} transform - The pre-transform to apply. * @returns {RouteObject} The RouteObject with the pre-transform applied. @@ -382,7 +384,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* transform(glm::mat4 transform); /**jsdoc - * Filter {@link Pose} route values to have a post-transform applied. + * Filters {@link Pose} route values to have a post-transform applied. * @function RouteObject#postTransform * @param {Mat4} transform - The post-transform to apply. * @returns {RouteObject} The RouteObject with the post-transform applied. @@ -391,7 +393,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* postTransform(glm::mat4 transform); /**jsdoc - * Filter {@link Pose} route values to have a pre-rotation applied. + * Filters {@link Pose} route values to have a pre-rotation applied. * @function RouteObject#rotate * @param {Quat} rotation - The pre-rotation to add to the pose. * @returns {RouteObject} The RouteObject with the pre-rotation applied. @@ -400,7 +402,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* rotate(glm::quat rotation); /**jsdoc - * Filter {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation + * Filters {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation * values are calculated as: (1 - f) * currentValue + f * previousValue where * f = currentVelocity / filterConstant. At low velocities, the filter value is largely the previous * value; at high velocities the value is wholly the current controller value. @@ -415,7 +417,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant); /**jsdoc - * Filter {@link Pose} route values to be smoothed by an exponential decay filter. The filter's rotation and + * Filters {@link Pose} route values to be smoothed by an exponential decay filter. The filter's rotation and * translation values are calculated as: filterConstant * currentValue + (1 - filterConstant) * * previousValue. Values near 1 are less smooth with lower latency; values near 0 are more smooth with higher * latency. @@ -428,7 +430,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant); /**jsdoc - * Filter numeric route values such that a value of 0.0 is changed to 1.0, and other values + * Filters numeric route values such that a value of 0.0 is changed to 1.0, and other values * are changed to 0.0. * @function RouteObject#logicalNot * @returns {RouteObject} The RouteObject with the filter applied. diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 6cfff7bc41..7d130125b3 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -205,8 +205,11 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { foreach (const EntityItemID& entityID, entitiesWithEntityScripts) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); - if (entityItem) { + if (entityItem && !entityItem->getScript().isEmpty()) { if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { + if (entityItem->contains(_avatarPosition)) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } _entitiesScriptEngine->unloadEntityScript(entityID, true); } } @@ -534,7 +537,7 @@ void EntityTreeRenderer::handleSpaceUpdate(std::pair proxyUp _spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second); } -bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar) { +bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet& entitiesContainingAvatar) { bool didUpdate = false; float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later QVector entityIDs; @@ -580,9 +583,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVectorgetEntityItemID(); - } + entitiesContainingAvatar << entity->getEntityItemID(); } } } @@ -616,36 +617,36 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { auto movedEnough = glm::distance(avatarPosition, _avatarPosition) > ZONE_CHECK_DISTANCE; auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL; - if (movedEnough || enoughTimeElapsed) { + if (_forceRecheckEntities || movedEnough || enoughTimeElapsed) { _avatarPosition = avatarPosition; _lastZoneCheck = now; - QVector entitiesContainingAvatar; - didUpdate = findBestZoneAndMaybeContainingEntities(&entitiesContainingAvatar); - + _forceRecheckEntities = false; + + QSet entitiesContainingAvatar; + didUpdate = findBestZoneAndMaybeContainingEntities(entitiesContainingAvatar); + // Note: at this point we don't need to worry about the tree being locked, because we only deal with // EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts // for entity IDs that no longer exist. - // for all of our previous containing entities, if they are no longer containing then send them a leave event - foreach(const EntityItemID& entityID, _currentEntitiesInside) { - if (!entitiesContainingAvatar.contains(entityID)) { - emit leaveEntity(entityID); - if (_entitiesScriptEngine) { + if (_entitiesScriptEngine) { + // for all of our previous containing entities, if they are no longer containing then send them a leave event + foreach(const EntityItemID& entityID, _currentEntitiesInside) { + if (!entitiesContainingAvatar.contains(entityID)) { + emit leaveEntity(entityID); _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } - } - // for all of our new containing entities, if they weren't previously containing then send them an enter event - foreach(const EntityItemID& entityID, entitiesContainingAvatar) { - if (!_currentEntitiesInside.contains(entityID)) { - emit enterEntity(entityID); - if (_entitiesScriptEngine) { + // for all of our new containing entities, if they weren't previously containing then send them an enter event + foreach(const EntityItemID& entityID, entitiesContainingAvatar) { + if (!_currentEntitiesInside.contains(entityID)) { + emit enterEntity(entityID); _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); } } + _currentEntitiesInside = entitiesContainingAvatar; } - _currentEntitiesInside = entitiesContainingAvatar; } } return didUpdate; @@ -653,7 +654,7 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { if (_tree && !_shuttingDown) { - QVector currentEntitiesInsideToSave; + QSet currentEntitiesInsideToSave; foreach (const EntityItemID& entityID, _currentEntitiesInside) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { @@ -662,7 +663,7 @@ void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } else { - currentEntitiesInsideToSave.push_back(entityID); + currentEntitiesInsideToSave.insert(entityID); } } @@ -687,9 +688,7 @@ void EntityTreeRenderer::leaveAllEntities() { } void EntityTreeRenderer::forceRecheckEntities() { - // make sure our "last avatar position" is something other than our current position, - // so that on our next chance, we'll check for enter/leave entity events. - _avatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); + _forceRecheckEntities = true; } bool EntityTreeRenderer::applyLayeredZones() { @@ -992,7 +991,10 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { return; } - if (_tree && !_shuttingDown && _entitiesScriptEngine) { + if (_tree && !_shuttingDown && _entitiesScriptEngine && !itr->second->getEntity()->getScript().isEmpty()) { + if (itr->second->getEntity()->contains(_avatarPosition)) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } _entitiesScriptEngine->unloadEntityScript(entityID, true); } @@ -1038,6 +1040,9 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool QString scriptUrl = entity->getScript(); if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) { if (_entitiesScriptEngine) { + if (entity->contains(_avatarPosition)) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } _entitiesScriptEngine->unloadEntityScript(entityID); } entity->scriptHasUnloaded(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index cee91ad1c7..05105a2c7f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -169,7 +169,7 @@ private: void resetEntitiesScriptEngine(); - bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); + bool findBestZoneAndMaybeContainingEntities(QSet& entitiesContainingAvatar); bool applyLayeredZones(); void stopDomainAndNonOwnedEntities(); @@ -186,7 +186,8 @@ private: void forceRecheckEntities(); glm::vec3 _avatarPosition { 0.0f }; - QVector _currentEntitiesInside; + bool _forceRecheckEntities { true }; + QSet _currentEntitiesInside; bool _wantScripts; ScriptEnginePointer _entitiesScriptEngine; diff --git a/libraries/fbx/src/FBXSerializer_Node.cpp b/libraries/fbx/src/FBXSerializer_Node.cpp index f9ef84c6f2..717bc113dd 100644 --- a/libraries/fbx/src/FBXSerializer_Node.cpp +++ b/libraries/fbx/src/FBXSerializer_Node.cpp @@ -41,8 +41,14 @@ QVariant readBinaryArray(QDataStream& in, int& position) { quint32 compressedLength; in >> arrayLength; + if (arrayLength > std::numeric_limits::max() / sizeof(T)) { // Upcoming byte containers are limited to max signed int + throw QString("FBX file most likely corrupt: binary data exceeds data limits"); + } in >> encoding; in >> compressedLength; + if (compressedLength > std::numeric_limits::max() / sizeof(T)) { // Upcoming byte containers are limited to max signed int + throw QString("FBX file most likely corrupt: compressed binary data exceeds data limits"); + } position += sizeof(quint32) * 3; QVector values; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 3383d71a52..0a6c76e456 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -219,9 +219,10 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic /**jsdoc *

The Controller.Hardware.Keyboard object has properties representing keyboard, mouse, and display touch - * events. The property values are integer IDs, uniquely identifying each output. Read-only. These can be mapped to - * actions or functions or Controller.Standard items in a {@link RouteObject} mapping. For presses, each data - * value is either 1.0 for "true" or 0.0 for "false".

+ * events. The property values are integer IDs, uniquely identifying each output. Read-only.

+ *

These events can be mapped to actions or functions or Controller.Standard items in a {@link RouteObject} + * mapping. For presses, each data value is either 1.0 for "true" or 0.0 for "false".

+ * *
RightThumbUpnumbernumberRight thumb not touching primary or secondary * thumb buttons.
LeftPrimaryIndexnumbernumberLeft primary index control pressed. - * To Do: Implement this for current controllers.
LeftPrimaryIndexnumbernumberLeft primary index control + * pressed.
LeftSecondaryIndexnumbernumberLeft secondary index control pressed. *
RightPrimaryIndexnumbernumberRight primary index control pressed. - * To Do: Implement this for current controllers.
RightSecondaryIndexnumbernumberRight secondary index control pressed. *
LeftPrimaryIndexTouchnumbernumberLeft index finger is touching primary diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 845e19f6c3..f0a823a3de 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -84,12 +84,12 @@ class UserInputMapper; /**jsdoc * A route in a {@link Controller.MappingJSON}. * @typedef {object} Controller.MappingJSONRoute - * @property {string|Controller.MappingJSONAxis} from - The name of a {@link Controller.Hardware} property name or an axis - * made from them. If a property name, the leading "Controller.Hardware." can be omitted. - * @property {boolean} [peek=false] - If true then peeking is enabled per {@link RouteObject#peek}. - * @property {boolean} [debug=false] - If true then debug is enabled per {@link RouteObject#debug}. + * @property {string|Controller.MappingJSONAxis} from - The name of a {@link Controller.Hardware} property or an axis made from + * them. If a property name, the leading "Controller.Hardware." can be omitted. + * @property {boolean} [peek=false] - If true, then peeking is enabled per {@link RouteObject#peek}. + * @property {boolean} [debug=false] - If true, then debug is enabled per {@link RouteObject#debug}. * @property {string|string[]} [when=[]] - One or more numeric {@link Controller.Hardware} property names which are evaluated - * as booleans and ANDed together. Prepend with a ! to use the logical NOT of the property value. The leading + * as booleans and ANDed together. Prepend a property name with a ! to do a logical NOT. The leading * "Controller.Hardware." can be omitted from the property names. * @property {Controller.MappingJSONFilter|Controller.MappingJSONFilter[]} [filters=[]] - One or more filters in the route. * @property {string} to - The name of a {@link Controller.Actions} or {@link Controller.Standard} property. The leading @@ -134,7 +134,7 @@ public: : _parent(parent), _mapping(mapping) { } /**jsdoc - * Create a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or + * Creates a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or * function.
* This is a QML-specific version of {@link MappingObject#from|from}: use this version in QML files. * @function MappingObject#fromQml @@ -145,7 +145,7 @@ public: Q_INVOKABLE QObject* fromQml(const QJSValue& source); /**jsdoc - * Create a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative + * Creates a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative * direction and the other in the positive direction, ready to be mapped to a standard control, action, or function.
* This is a QML-specific version of {@link MappingObject#makeAxis|makeAxis}: use this version in QML files. * @function MappingObject#makeAxisQml @@ -157,7 +157,7 @@ public: Q_INVOKABLE QObject* makeAxisQml(const QJSValue& source1, const QJSValue& source2); /**jsdoc - * Create a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or + * Creates a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or * function. * @function MappingObject#from * @param {Controller.Standard|Controller.Hardware|function} source - The controller output or function that is the source @@ -167,7 +167,7 @@ public: Q_INVOKABLE QObject* from(const QScriptValue& source); /**jsdoc - * Create a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative + * Creates a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative * direction and the other in the positive direction, ready to be mapped to a standard control, action, or function. * @function MappingObject#makeAxis * @param {Controller.Hardware} source1 - The first, negative-direction controller output. @@ -189,7 +189,7 @@ public: Q_INVOKABLE QObject* makeAxis(const QScriptValue& source1, const QScriptValue& source2); /**jsdoc - * Enable or disable the mapping. When enabled, the routes in the mapping take effect.
+ * Enables or disables the mapping. When enabled, the routes in the mapping take effect.
* Synonymous with {@link Controller.enableMapping}. * @function MappingObject#enable * @param {boolean} enable=true - If true then the mapping is enabled, otherwise it is disabled. @@ -198,7 +198,7 @@ public: Q_INVOKABLE QObject* enable(bool enable = true); /**jsdoc - * Disable the mapping. When disabled, the routes in the mapping have no effect.
+ * Disables the mapping. When disabled, the routes in the mapping have no effect.
* Synonymous with {@link Controller.disableMapping}. * @function MappingObject#disable * @returns {MappingObject} The mapping object, so that further routes can be added. diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 048e23be1c..56ace23335 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -66,6 +66,8 @@ QObject* RouteBuilderProxy::peek(bool enable) { } QObject* RouteBuilderProxy::when(const QScriptValue& expression) { + // FIXME: Support "!" conditional in simple expression and array expression. + // Note that "!" is supported when parsing a JSON file, in UserInputMapper::parseConditional(). auto newConditional = _parent.conditionalFor(expression); if (_route->conditional) { _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index eb610af78a..e7ff04d72c 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -51,7 +51,7 @@ class RouteBuilderProxy : public QObject { : _parent(parent), _mapping(mapping), _route(route) { } /**jsdoc - * Terminate the route with a standard control, an action, or a script function. The output value from the route is + * Terminates the route with a standard control, an action, or a script function. The output value from the route is * sent to the specified destination.
* This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files. * @function RouteObject#toQml @@ -62,7 +62,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE void toQml(const QJSValue& destination); /**jsdoc - * Process the route only if a condition is satisfied. The condition is evaluated before the route input is read, and + * Processes the route only if a condition is satisfied. The condition is evaluated before the route input is read, and * the input is read only if the condition is true. Thus, if the condition is not met then subsequent * routes using the same input are processed.
* This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files. @@ -81,7 +81,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* whenQml(const QJSValue& expression); /**jsdoc - * Terminate the route with a standard control, an action, or a script function. The output value from the route is + * Terminates the route with a standard control, an action, or a script function. The output value from the route is * sent to the specified destination. * @function RouteObject#to * @param {Controller.Standard|Controller.Actions|function} destination - The standard control, action, or JavaScript @@ -117,7 +117,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE void to(const QScriptValue& destination); /**jsdoc - * Enable and disabling writing debug information for a route to the program log. + * Enables or disables writing debug information for a route to the program log. * @function RouteObject#debug * @param {boolean} [enable=true] - If true then writing debug information is enabled for the route, * otherwise it is disabled. @@ -147,7 +147,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* debug(bool enable = true); /**jsdoc - * Process the route without marking the controller output as having been read, so that other routes from the same + * Processes the route without marking the controller output as having been read, so that other routes from the same * controller output can also process. * @function RouteObject#peek * @param {boolean} [enable=true] - If true then the route is processed without marking the route's @@ -157,7 +157,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* peek(bool enable = true); /**jsdoc - * Process the route only if a condition is satisfied. The condition is evaluated before the route input is read, and + * Processes the route only if a condition is satisfied. The condition is evaluated before the route input is read, and * the input is read only if the condition is true. Thus, if the condition is not met then subsequent * routes using the same input are processed. * @function RouteObject#when @@ -170,6 +170,8 @@ class RouteBuilderProxy : public QObject { * definition. * *

If an array of conditions is provided, their values are ANDed together.

+ *

Warning: The use of ! is not currently supported in JavaScript .when() + * calls.

* @returns {RouteObject} The RouteObject with the condition added. * @example
Process the right trigger differently in HMD and desktop modes.
* * @@ -269,14 +270,18 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic * new x-coordinate value. * - * * * + * is the number of units rotated (typically 1.0).
+ * Warning: The mouse wheel in an ordinary mouse generates left/right wheel events instead of + * up/down. * + * is the number of units rotated (typically 1.0).
+ * Warning: The mouse wheel in an ordinary mouse generates left/right wheel events instead of + * up/down. * *
PropertyTypeDataDescription
MouseYnumbernumberThe mouse y-coordinate changed. The data value is its * new y-coordinate value.
MouseWheelRightnumbernumberThe mouse wheel rotated left. The data value + *
MouseWheelRightnumbernumberThe mouse wheel rotated right. The data value * is the number of units rotated (typically 1.0).
MouseWheelLeftnumbernumberThe mouse wheel rotated left. The data value * is the number of units rotated (typically 1.0).
MouseWheelUpnumbernumberThe mouse wheel rotated up. The data value - * is the number of units rotated (typically 1.0).
MouseWheelDownnumbernumberThe mouse wheel rotated down. The data value - * is the number of units rotated (typically 1.0).
TouchpadRightnumbernumberThe average touch on a touch-enabled device * moved right. The data value is how far the average position of all touch points moved.
TouchpadLeftnumbernumberThe average touch on a touch-enabled device @@ -288,7 +293,6 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic * *
* @typedef {object} Controller.Hardware-Keyboard - * @todo Currently, the mouse wheel in an ordinary mouse generates left/right wheel events instead of up/down. */ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInputs() const { using namespace controller; diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 1f190111f2..7f75b3bfb9 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -261,6 +261,61 @@ void Midi::MidiCleanup() { } #endif +/**jsdoc + * A MIDI message. + *

Warning: The status property is NOT a MIDI status value.

+ * @typedef {object} Midi.MidiMessage + * @property {number} device - Device number. + * @property {Midi.RawMidiMessage} raw - Raw MIDI message. + * @property {number} status - Channel + status. Legacy value. + * @property {number} channel - Channel: 116. + * @property {number} type - Status: {@link Midi.MidiStatus}; 815. + * @property {number} note - Note: 0127. + * @property {number} velocity - Note velocity: 0127. (0 means "note off".) + * @property {number} bend - Pitch bend: -81928191. + * @property {number} program - Program change: 0127. + */ +/**jsdoc + * An integer DWORD (unsigned 32 bit) message with bits having values as follows: + * + * + * + * + * + * + * + * + * + *
000000000vvvvvvv0nnnnnnn1ssscccc
+ *

Where:

+ *
    + *
  • v = Velocity. + *
  • n = Note. + *
  • s = Status - {@link Midi.MidiStatus} + *
  • c = Channel. + *
+ *

The number in the first bit of each byte denotes whether it is a command (1) or data (0). + * @typedef {number} Midi.RawMidiMessage + */ +/**jsdoc + *

A MIDI status value. The following MIDI status values are supported:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
8Note off.
9Note on.
10Polyphonic key pressure.
11Control change.
12Program change.
13Channel pressure.
14Pitch bend.
15System message.
+ * @typedef {number} Midi.MidiStatus + */ void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { QVariantMap eventData; eventData["device"] = device; diff --git a/libraries/midi/src/Midi.h b/libraries/midi/src/Midi.h index 081a44f7b6..dd2b5fd678 100644 --- a/libraries/midi/src/Midi.h +++ b/libraries/midi/src/Midi.h @@ -21,6 +21,12 @@ #include /**jsdoc + * The Midi API provides the ability to connect Interface with musical instruments and other external or virtual + * devices via the MIDI protocol. For further information and examples, see the tutorial: + * Use MIDI to Control Your Environment.

+ * + *

Note: Only works on Windows.

+ * * @namespace Midi * * @hifi-interface @@ -49,88 +55,112 @@ private: void MidiCleanup(); signals: - void midiNote(QVariantMap eventData); - void midiMessage(QVariantMap eventData); - void midiReset(); - - public slots: /**jsdoc - * Send Raw MIDI packet to a particular device. + * Triggered when a connected device sends an output. + * @function Midi.midiNote + * @param {Midi.MidiMessage} message - The MIDI message. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. Use {@link Midi.midiMessage|midiMessage} instead. + */ + void midiNote(QVariantMap eventData); + + /**jsdoc + * Triggered when a connected device sends an output. + * @function Midi.midiMessage + * @param {Midi.MidiMessage} message - The MIDI message. + * @returns {Signal} + */ + void midiMessage(QVariantMap eventData); + + /**jsdoc + * Triggered when the system detects there was a reset such as when a device is plugged in or unplugged. + * @function Midi.midiReset + * @returns {Signal} + */ + void midiReset(); + +public slots: + + /**jsdoc + * Sends a raw MIDI packet to a particular device. * @function Midi.sendRawDword * @param {number} device - Integer device number. - * @param {number} raw - Integer (DWORD) raw MIDI message. + * @param {Midi.RawMidiMessage} raw - Raw MIDI message. */ Q_INVOKABLE void sendRawDword(int device, int raw); /**jsdoc - * Send MIDI message to a particular device. + * Sends a MIDI message to a particular device. * @function Midi.sendMidiMessage * @param {number} device - Integer device number. * @param {number} channel - Integer channel number. - * @param {number} type - 0x8 is note off, 0x9 is note on (if velocity=0, note off), etc. - * @param {number} note - MIDI note number. - * @param {number} velocity - Note velocity (0 means note off). + * @param {Midi.MidiStatus} type - Integer status value. + * @param {number} note - Note number. + * @param {number} velocity - Note velocity. (0 means "note off".) + * @comment The "type" parameter has that name to match up with {@link Midi.MidiMessage}. */ Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity); /**jsdoc - * Play a note on all connected devices. + * Plays a note on all connected devices. * @function Midi.playMidiNote - * @param {number} status - 0x80 is note off, 0x90 is note on (if velocity=0, note off), etc. - * @param {number} note - MIDI note number. - * @param {number} velocity - Note velocity (0 means note off). + * @param {MidiStatus} status - Note status. + * @param {number} note - Note number. + * @param {number} velocity - Note velocity. (0 means "note off".) */ Q_INVOKABLE void playMidiNote(int status, int note, int velocity); /**jsdoc - * Turn off all notes on all connected devices. + * Turns off all notes on all connected MIDI devices. * @function Midi.allNotesOff */ Q_INVOKABLE void allNotesOff(); /**jsdoc - * Clean up and re-discover attached devices. + * Cleans up and rediscovers attached MIDI devices. * @function Midi.resetDevices */ Q_INVOKABLE void resetDevices(); /**jsdoc - * Get a list of inputs/outputs. + * Gets a list of MIDI input or output devices. * @function Midi.listMidiDevices - * @param {boolean} output + * @param {boolean} output - true to list output devices, false to list input devices. * @returns {string[]} */ Q_INVOKABLE QStringList listMidiDevices(bool output); /**jsdoc - * Block an input/output by name. + * Blocks a MIDI device's input or output. * @function Midi.blockMidiDevice - * @param {string} name - * @param {boolean} output + * @param {string} name - The name of the MIDI device to block. + * @param {boolean} output - true to block the device's output, false to block its input. */ Q_INVOKABLE void blockMidiDevice(QString name, bool output); /**jsdoc - * Unblock an input/output by name. + * Unblocks a MIDI device's input or output. * @function Midi.unblockMidiDevice - * @param {string} name - * @param {boolean} output + * @param {string} name- The name of the MIDI device to unblock. + * @param {boolean} output - true to unblock the device's output, false to unblock its input. */ Q_INVOKABLE void unblockMidiDevice(QString name, bool output); /**jsdoc - * Repeat all incoming notes to all outputs (default disabled). + * Enables or disables repeating all incoming notes to all outputs. (Default is disabled.) * @function Midi.thruModeEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable repeating all incoming notes to all output, false to + * disable. */ Q_INVOKABLE void thruModeEnable(bool enable); /**jsdoc - * Broadcast on all unblocked devices. + * Enables or disables broadcasts to all unblocked devices. * @function Midi.broadcastEnable - * @param {boolean} enable + * @param {boolean} enable - true to have "send" functions broadcast to all devices, false to + * have them send to specific output devices. */ Q_INVOKABLE void broadcastEnable(bool enable); @@ -138,50 +168,58 @@ signals: /// filter by event types /**jsdoc + * Enables or disables note off events. * @function Midi.typeNoteOffEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typeNoteOffEnable(bool enable); /**jsdoc + * Enables or disables note on events. * @function Midi.typeNoteOnEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typeNoteOnEnable(bool enable); /**jsdoc + * Enables or disables poly key pressure events. * @function Midi.typePolyKeyPressureEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typePolyKeyPressureEnable(bool enable); /**jsdoc + * Enables or disables control change events. * @function Midi.typeControlChangeEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typeControlChangeEnable(bool enable); /**jsdoc + * Enables or disables program change events. * @function Midi.typeProgramChangeEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typeProgramChangeEnable(bool enable); /**jsdoc + * Enables or disables channel pressure events. * @function Midi.typeChanPressureEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typeChanPressureEnable(bool enable); /**jsdoc + * Enables or disables pitch bend events. * @function Midi.typePitchBendEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typePitchBendEnable(bool enable); /**jsdoc + * Enables or disables system message events. * @function Midi.typeSystemMessageEnable - * @param {boolean} enable + * @param {boolean} enable - true to enable, false to disable. */ Q_INVOKABLE void typeSystemMessageEnable(bool enable); diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index c0ad89e804..37b0165186 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -29,7 +29,7 @@ void CongestionControl::setMaxBandwidth(int maxBandwidth) { void CongestionControl::setPacketSendPeriod(double newSendPeriod) { Q_ASSERT_X(newSendPeriod >= 0, "CongestionControl::setPacketPeriod", "Can not set a negative packet send period"); - auto packetsPerSecond = (double)_maxBandwidth / (BITS_PER_BYTE * _mss); + auto packetsPerSecond = _mss > 0 ? (double)_maxBandwidth / (BITS_PER_BYTE * _mss) : -1.0; if (packetsPerSecond > 0.0) { // anytime the packet send period is about to be increased, make sure it stays below the minimum period, // calculated based on the maximum desired bandwidth diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 861774b145..f8574b3b94 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,10 +38,10 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: - return static_cast(AvatarMixerPacketVersion::HandControllerSection); + return static_cast(AvatarMixerPacketVersion::SendVerificationFailed); case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::HandControllerSection); + return static_cast(AvatarMixerPacketVersion::SendVerificationFailed); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5de7e56e47..96223b7eea 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -334,6 +334,7 @@ enum class AvatarMixerPacketVersion : PacketVersion { SendMaxTranslationDimension, FBXJointOrderChange, HandControllerSection, + SendVerificationFailed }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 7cad337dd5..c700f1ad3f 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -237,9 +237,11 @@ void CauterizedModel::updateRenderItems() { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions, cauterizedMeshState.clusterDualQuaternions); + data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); } else { data.updateClusterBuffer(meshState.clusterMatrices, cauterizedMeshState.clusterMatrices); + data.computeAdjustedLocalBound(meshState.clusterMatrices); } Transform renderTransform = modelTransform; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 7be5f93a95..cb3aa76468 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -450,9 +450,9 @@ void ModelMeshPartPayload::render(RenderArgs* args) { void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { _adjustedLocalBound = _localBound; if (clusterMatrices.size() > 0) { - _adjustedLocalBound.transform(clusterMatrices[0]); + _adjustedLocalBound.transform(clusterMatrices.back()); - for (int i = 1; i < (int)clusterMatrices.size(); ++i) { + for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) { AABox clusterBound = _localBound; clusterBound.transform(clusterMatrices[i]); _adjustedLocalBound += clusterBound; @@ -463,12 +463,12 @@ void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions) { _adjustedLocalBound = _localBound; if (clusterDualQuaternions.size() > 0) { - Transform rootTransform(clusterDualQuaternions[0].getRotation(), - clusterDualQuaternions[0].getScale(), - clusterDualQuaternions[0].getTranslation()); + Transform rootTransform(clusterDualQuaternions.back().getRotation(), + clusterDualQuaternions.back().getScale(), + clusterDualQuaternions.back().getTranslation()); _adjustedLocalBound.transform(rootTransform); - for (int i = 1; i < (int)clusterDualQuaternions.size(); ++i) { + for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) { AABox clusterBound = _localBound; Transform transform(clusterDualQuaternions[i].getRotation(), clusterDualQuaternions[i].getScale(), diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e2d78a8d94..b24c1a01cc 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -241,8 +241,10 @@ void Model::updateRenderItems() { invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); + data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); } else { data.updateClusterBuffer(meshState.clusterMatrices); + data.computeAdjustedLocalBound(meshState.clusterMatrices); } Transform renderTransform = modelTransform; @@ -774,11 +776,14 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { auto& materialName = _modelMeshMaterialNames[shapeID]; result.appendMaterial(graphics::MaterialLayer(getGeometry()->getShapeMaterial(shapeID), 0), shapeID, materialName); - auto mappedMaterialIter = _materialMapping.find(shapeID); - if (mappedMaterialIter != _materialMapping.end()) { - auto mappedMaterials = mappedMaterialIter->second; - for (auto& mappedMaterial : mappedMaterials) { - result.appendMaterial(mappedMaterial, shapeID, materialName); + { + std::unique_lock lock(_materialMappingMutex); + auto mappedMaterialIter = _materialMapping.find(shapeID); + if (mappedMaterialIter != _materialMapping.end()) { + auto mappedMaterials = mappedMaterialIter->second; + for (auto& mappedMaterial : mappedMaterials) { + result.appendMaterial(mappedMaterial, shapeID, materialName); + } } } shapeID++; @@ -1367,8 +1372,6 @@ void Model::simulate(float deltaTime, bool fullUpdate) { // update the world space transforms for all joints glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); updateRig(deltaTime, parentTransform); - - computeMeshPartLocalBounds(); } } @@ -1379,17 +1382,6 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig.updateAnimations(deltaTime, parentTransform, rigToWorldTransform); } -void Model::computeMeshPartLocalBounds() { - for (auto& part : _modelMeshRenderItems) { - const Model::MeshState& state = _meshStates.at(part->_meshIndex); - if (_useDualQuaternionSkinning) { - part->computeAdjustedLocalBound(state.clusterDualQuaternions); - } else { - part->computeAdjustedLocalBound(state.clusterMatrices); - } - } -} - // virtual void Model::updateClusterMatrices() { DETAILED_PERFORMANCE_TIMER("Model::updateClusterMatrices"); @@ -1569,6 +1561,13 @@ void Model::applyMaterialMapping() { auto renderItemsKey = _renderItemKeyGlobalFlags; PrimitiveMode primitiveMode = getPrimitiveMode(); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; + auto modelMeshRenderItemIDs = _modelMeshRenderItemIDs; + auto modelMeshRenderItemShapes = _modelMeshRenderItemShapes; + std::unordered_map shouldInvalidatePayloadShapeKeyMap; + + for (auto& shape : _modelMeshRenderItemShapes) { + shouldInvalidatePayloadShapeKeyMap[shape.meshIndex] = shouldInvalidatePayloadShapeKey(shape.meshIndex); + } auto& materialMapping = getMaterialMapping(); for (auto& mapping : materialMapping) { @@ -1588,7 +1587,8 @@ void Model::applyMaterialMapping() { priorityMapPerResource[shapeID] = ++_priorityMap[shapeID]; } - auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() { + auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning, + modelMeshRenderItemIDs, modelMeshRenderItemShapes, shouldInvalidatePayloadShapeKeyMap]() { if (networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) { return; } @@ -1611,12 +1611,15 @@ void Model::applyMaterialMapping() { } } for (auto shapeID : shapeIDs) { - if (shapeID < _modelMeshRenderItemIDs.size()) { - auto itemID = _modelMeshRenderItemIDs[shapeID]; - auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; - bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); + if (shapeID < modelMeshRenderItemIDs.size()) { + auto itemID = modelMeshRenderItemIDs[shapeID]; + auto meshIndex = modelMeshRenderItemShapes[shapeID].meshIndex; + bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKeyMap.at(meshIndex); graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID)); - _materialMapping[shapeID].push_back(material); + { + std::unique_lock lock(_materialMappingMutex); + _materialMapping[shapeID].push_back(material); + } transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.addMaterial(material); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 1431b5e3f9..7844e9bc41 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -379,6 +379,7 @@ protected: std::unordered_map _priorityMap; // only used for materialMapping std::unordered_map> _materialMapping; // generated during applyMaterialMapping + std::mutex _materialMappingMutex; void applyMaterialMapping(); void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } @@ -427,7 +428,6 @@ protected: void setScaleInternal(const glm::vec3& scale); void snapToRegistrationPoint(); - void computeMeshPartLocalBounds(); virtual void updateRig(float deltaTime, glm::mat4 parentTransform); /// Allow sub classes to force invalidating the bboxes diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 16d1d49d9f..4f6fe3e36a 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -392,6 +392,10 @@ void Scene::updateItems(const Transaction::Updates& transactions) { void Scene::transitionItems(const Transaction::TransitionAdds& transactions) { auto transitionStage = getStage(TransitionStage::getName()); + if (!transitionStage) { + return; + } + for (auto& add : transactions) { auto itemId = std::get<0>(add); // Access the true item @@ -433,6 +437,10 @@ void Scene::reApplyTransitions(const Transaction::TransitionReApplies& transacti void Scene::queryTransitionItems(const Transaction::TransitionQueries& transactions) { auto transitionStage = getStage(TransitionStage::getName()); + if (!transitionStage) { + return; + } + for (auto& query : transactions) { auto itemId = std::get<0>(query); // Access the true item @@ -553,11 +561,14 @@ void Scene::setItemTransition(ItemID itemId, Index transitionId) { } void Scene::resetItemTransition(ItemID itemId) { + auto transitionStage = getStage(TransitionStage::getName()); + if (!transitionStage) { + return; + } + auto& item = _items[itemId]; TransitionStage::Index transitionId = item.getTransitionId(); if (!render::TransitionStage::isIndexInvalid(transitionId)) { - auto transitionStage = getStage(TransitionStage::getName()); - auto finishedOperators = _transitionFinishedOperatorMap[transitionId]; for (auto finishedOperator : finishedOperators) { if (finishedOperator) { diff --git a/libraries/shared/src/ResourceRequestObserver.cpp b/libraries/shared/src/ResourceRequestObserver.cpp index 608d6905c5..21f4d56173 100644 --- a/libraries/shared/src/ResourceRequestObserver.cpp +++ b/libraries/shared/src/ResourceRequestObserver.cpp @@ -16,6 +16,13 @@ #include #include +/**jsdoc + * Information about a resource request. + * @typedef {object} ResourceRequestObserver.ResourceRequest + * @property {string} url - The URL of the resource request. + * @property {number} callerId - An ID identifying the request. + * @property {string} extra - Extra information about the request. + */ void ResourceRequestObserver::update(const QUrl& requestUrl, const qint64 callerId, const QString& extra) { diff --git a/libraries/shared/src/ResourceRequestObserver.h b/libraries/shared/src/ResourceRequestObserver.h index edf3c617cb..352f01c3a5 100644 --- a/libraries/shared/src/ResourceRequestObserver.h +++ b/libraries/shared/src/ResourceRequestObserver.h @@ -16,7 +16,15 @@ #include "DependencyManager.h" - +/**jsdoc + * The ResourceRequestObserver API provides notifications when an observable resource request is made. + * + * @namespace ResourceRequestObserver + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + */ class ResourceRequestObserver : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -25,5 +33,29 @@ public: void update(const QUrl& requestUrl, const qint64 callerId = -1, const QString& extra = ""); signals: + /**jsdoc + * Triggered when an observable resource request is made. + * @function ResourceRequestObserver.resourceRequestEvent + * @param {ResourceRequestObserver.ResourceRequest} request - Information about the resource request. + * @returns {Signal} + * @example Report when a particular Clipboard.importEntities() resource request is made. + * ResourceRequestObserver.resourceRequestEvent.connect(function (request) { + * if (request.callerId === 100) { + * print("Resource request: " + JSON.stringify(request)); + * } + * }); + * + * function importEntities() { + * var filename = Window.browse("Import entities to clipboard", "", "*.json"); + * if (filename) { + * Clipboard.importEntities(filename, true, 100); + * pastedEntities = Clipboard.pasteEntities(Vec3.sum(MyAvatar.position, + * Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 }))); + * print("Entities pasted: " + JSON.stringify(pastedEntities)); + * } + * } + * + * Script.setTimeout(importEntities, 2000); + */ void resourceRequestEvent(QVariantMap result); }; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 2f2d38fe2a..1e6a01c187 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -252,7 +252,9 @@ private slots: _finished = true; auto offscreenUi = DependencyManager::get(); emit response(_result); - offscreenUi->removeModalDialog(qobject_cast(this)); + if (!offscreenUi.isNull()) { + offscreenUi->removeModalDialog(qobject_cast(this)); + } disconnect(_dialog); } }; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 8d97ff78af..14830f3f04 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -409,9 +409,10 @@ void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) { } /**jsdoc - *

The Controller.Hardware.OculusTouch object has properties representing Oculus Rift. The property values are - * integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or - * Controller.Standard items in a {@link RouteObject} mapping.

+ *

The Controller.Hardware.OculusTouch object has properties representing the Oculus Rift. The property values + * are integer IDs, uniquely identifying each output. Read-only.

+ *

These outputs can be mapped to actions or functions or Controller.Standard items in a {@link RouteObject} + * mapping.

* * * diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 34ebb73fda..c21a9ae4df 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -1299,14 +1299,20 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu } /**jsdoc - *

The Controller.Hardware.Vive object has properties representing Vive. The property values are integer IDs, - * uniquely identifying each output. Read-only. These can be mapped to actions or functions or - * Controller.Standard items in a {@link RouteObject} mapping.

+ *

The Controller.Hardware.Vive object has properties representing the Vive. The property values are integer + * IDs, uniquely identifying each output. Read-only.

+ *

These outputs can be mapped to actions or functions or Controller.Standard items in a {@link RouteObject} + * mapping.

*
PropertyTypeDataDescription
* * * * + * + * + * * * * diff --git a/prebuild.py b/prebuild.py index 5325ca34bc..b401c94e7f 100644 --- a/prebuild.py +++ b/prebuild.py @@ -94,7 +94,7 @@ def parse_args(): parser.add_argument('--vcpkg-root', type=str, help='The location of the vcpkg distribution') parser.add_argument('--build-root', required=True, type=str, help='The location of the cmake build') parser.add_argument('--ports-path', type=str, default=defaultPortsPath) - parser.add_argument('--ci-build', action='store_true') + parser.add_argument('--ci-build', action='store_true', default=os.getenv('CI_BUILD') is not None) if True: args = parser.parse_args() else: diff --git a/scripts/system/away.js b/scripts/system/away.js index e45041139a..5180588e9d 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -203,7 +203,7 @@ function setAwayProperties() { if (!wasMuted) { Audio.muted = !Audio.muted; } - MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view + MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view playAwayAnimation(); // animation is still seen by others showOverlay(); @@ -223,8 +223,8 @@ function setAwayProperties() { function setActiveProperties() { isAway = false; - if (!wasMuted) { - Audio.muted = !Audio.muted; + if (Audio.muted && !wasMuted) { + Audio.muted = false; } MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting. stopAwayAnimation(); @@ -254,7 +254,7 @@ function setActiveProperties() { } function maybeGoActive(event) { - if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it) + if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it) return; } if (!isAway && (event.text === 'ESC')) { @@ -314,6 +314,13 @@ function setEnabled(value) { isEnabled = value; } +function checkAudioToggled() { + if (isAway && !Audio.muted) { + goActive(); + } +} + + var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable"; var handleMessage = function(channel, message, sender) { if (channel === CHANNEL_AWAY_ENABLE && sender === MyAvatar.sessionUUID) { @@ -324,9 +331,10 @@ var handleMessage = function(channel, message, sender) { Messages.subscribe(CHANNEL_AWAY_ENABLE); Messages.messageReceived.connect(handleMessage); -var maybeIntervalTimer = Script.setInterval(function(){ +var maybeIntervalTimer = Script.setInterval(function() { maybeMoveOverlay(); maybeGoAway(); + checkAudioToggled(); }, BASIC_TIMER_INTERVAL); diff --git a/scripts/system/clickToAvatarApp.js b/scripts/system/clickToAvatarApp.js new file mode 100644 index 0000000000..8024f595b5 --- /dev/null +++ b/scripts/system/clickToAvatarApp.js @@ -0,0 +1,7 @@ +(function () { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + this.clickDownOnEntity = function (entityID, mouseEvent) { + tablet.loadQMLSource("hifi/AvatarApp.qml"); + }; +} +);
PropertyTypeDataDescription
Buttons
LeftApplicationMenunumbernumberLeft application menu button pressed. + *
RightApplicationMenunumbernumberRight application menu button pressed. + *
Touch Pad (Sticks)
LXnumbernumberLeft touch pad x-axis scale.
LYnumbernumberLeft touch pad y-axis scale.