diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index a109934d10..46599396ca 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -45,6 +45,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); + + auto nodeList = DependencyManager::get(); + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); } AvatarMixer::~AvatarMixer() { @@ -414,7 +417,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes AvatarData& avatar = nodeData->getAvatar(); // parse the identity packet and update the change timestamp if appropriate - if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) { + AvatarData::Identity identity; + AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); + if (avatar.processAvatarIdentity(identity)) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->flagIdentityChange(); } @@ -509,6 +514,19 @@ void AvatarMixer::domainSettingsRequestComplete() { _broadcastThread.start(); } +void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID) { + // if this client is using packet versions we don't expect. + if ((type == PacketTypeEnum::Value::AvatarIdentity || type == PacketTypeEnum::Value::AvatarData) && !senderUUID.isNull()) { + // Echo an empty AvatarData packet back to that client. + // This should trigger a version mismatch dialog on their side. + auto nodeList = DependencyManager::get(); + auto node = nodeList->nodeWithUUID(senderUUID); + if (node) { + auto emptyPacket = NLPacket::create(PacketType::AvatarData, 0); + nodeList->sendPacket(std::move(emptyPacket), *node); + } + } +} void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index c7761a2cba..d1a9249c83 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -38,7 +38,8 @@ private slots: void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); void domainSettingsRequestComplete(); - + void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + private: void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 61cc775e08..b940d46849 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -55,11 +55,19 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } - QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr()); + + QByteArray myProtocolVersion = protocolVersionsSignature(); + if (nodeConnection.protocolVersion != myProtocolVersion) { + QString protocolVersionError = "Protocol version mismatch - Domain version:" + QCoreApplication::applicationVersion(); + qDebug() << "Protocol Version mismatch - denying connection."; + sendConnectionDeniedPacket(protocolVersionError, message->getSenderSockAddr(), + DomainHandler::ConnectionRefusedReason::ProtocolMismatch); + return; + } if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; @@ -332,7 +340,7 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node bool DomainGatekeeper::verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr) { - + // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -370,7 +378,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, } else { if (!senderSockAddr.isNull()) { qDebug() << "Error decrypting username signature for " << username << "- denying connection."; - sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr); + sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } // free up the public key, we don't need it anymore @@ -382,13 +391,15 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, // we can't let this user in since we couldn't convert their public key to an RSA key we could use if (!senderSockAddr.isNull()) { qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; - sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr); + sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } } else { if (!senderSockAddr.isNull()) { qDebug() << "Insufficient data to decrypt username signature - denying connection."; - sendConnectionDeniedPacket("Insufficient data", senderSockAddr); + sendConnectionDeniedPacket("Insufficient data", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } @@ -402,7 +413,8 @@ bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByt if (username.isEmpty()) { qDebug() << "Connect request denied - no username provided."; - sendConnectionDeniedPacket("No username provided", senderSockAddr); + sendConnectionDeniedPacket("No username provided", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); return false; } @@ -416,7 +428,8 @@ bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByt } } else { qDebug() << "Connect request denied for user" << username << "- not in allowed users list."; - sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr); + sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::NotAuthorized); return false; } @@ -430,10 +443,10 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA // find out what our maximum capacity is const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; - + if (maximumUserCapacity > 0) { unsigned int connectedUsers = _server->countConnectedUsers(); - + if (connectedUsers >= maximumUserCapacity) { // too many users, deny the new connection unless this user is an allowed editor @@ -452,7 +465,8 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA // deny connection from this user qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; - sendConnectionDeniedPacket("Too many connected users.", senderSockAddr); + sendConnectionDeniedPacket("Too many connected users.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::TooManyUsers); return false; } @@ -516,16 +530,20 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { } } -void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr) { +void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); - + // setup the DomainConnectionDenied packet - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, + payloadSize + sizeof(payloadSize) + sizeof(uint8_t)); // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { + uint8_t reasonCodeWire = (uint8_t)reasonCode; + connectionDeniedPacket->writePrimitive(reasonCodeWire); connectionDeniedPacket->writePrimitive(payloadSize); connectionDeniedPacket->write(utfString); } diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index c4ac32fabf..09e3b04ed7 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -19,6 +19,8 @@ #include #include +#include + #include #include #include @@ -74,7 +76,8 @@ private: const HifiSockAddr& senderSockAddr); void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); - void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr); + void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown); void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index cfec72a24b..f6fbb3f470 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -303,6 +303,31 @@ const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; + + +bool DomainServer::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); + PacketVersion headerVersion = NLPacket::versionInHeader(packet); + + auto nodeList = DependencyManager::get(); + + // This implements a special case that handles OLD clients which don't know how to negotiate matching + // protocol versions. We know these clients will sent DomainConnectRequest with older versions. We also + // know these clients will show a warning dialog if they get an EntityData with a protocol version they + // don't understand, so we can send them an empty EntityData with our latest version and they will + // warn the user that the protocol is not compatible + if (headerType == PacketType::DomainConnectRequest && + headerVersion < static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { + auto packetWithBadVersion = NLPacket::create(PacketType::EntityData); + nodeList->sendPacket(std::move(packetWithBadVersion), packet.getSenderSockAddr()); + return false; + } + + // let the normal nodeList implementation handle all other packets. + return nodeList->isPacketVerified(packet); +} + + void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; @@ -376,6 +401,9 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); + + // set a custum packetVersionMatch as the verify packet operator for the udt::Socket + nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; @@ -666,7 +694,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { - + QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index fef3221b7d..c39e405380 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -99,6 +99,8 @@ private: void optionallyGetTemporaryName(const QStringList& arguments); + static bool packetVersionMatch(const udt::Packet& packet); + bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 28f769298c..13bb9123d8 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -19,6 +19,16 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c if (isConnectRequest) { dataStream >> newHeader.connectUUID; + + // Read out the protocol version signature from the connect message + char* rawBytes; + uint length; + + dataStream.readBytes(rawBytes, length); + newHeader.protocolVersion = QByteArray(rawBytes, length); + + // NOTE: QDataStream::readBytes() - The buffer is allocated using new []. Destroy it with the delete [] operator. + delete[] rawBytes; } dataStream >> newHeader.nodeType diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 34119ffdab..9264db637e 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -28,6 +28,8 @@ public: HifiSockAddr senderSockAddr; QList interestList; QString placeName; + + QByteArray protocolVersion; }; diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index 42237033af..066676140c 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -16,8 +16,10 @@ { "from": "Hydra.L0", "to": "Standard.Back" }, { "from": "Hydra.R0", "to": "Standard.Start" }, - { "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" }, - { "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.L1", "Hydra.L3" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" }, + { "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" }, { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, { "from": "Hydra.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/controllers/neuron.json b/interface/resources/controllers/neuron.json index 2d61f80c35..d0962c72db 100644 --- a/interface/resources/controllers/neuron.json +++ b/interface/resources/controllers/neuron.json @@ -1,7 +1,7 @@ { "name": "Neuron to Standard", "channels": [ - { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, - { "from": "Hydra.RightHand", "to": "Standard.RightHand" } + { "from": "Neuron.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Neuron.RightHand", "to": "Standard.RightHand" } ] } diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 4085d71c27..60a46ba3ce 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,24 +1,26 @@ { "name": "Vive to Standard", "channels": [ - { "from": "Vive.LY", "when": "Vive.LS", "filters": ["invert" ,{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT" }, { "from": "Vive.LeftGrip", "to": "Standard.LB" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RS", "filters": ["invert", { "type": "deadZone", "min": 0.6 }], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, { "from": "Vive.RightGrip", "to": "Standard.RB" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, - { "from": "Vive.LeftApplicationMenu", "to": "Standard.Back" }, - { "from": "Vive.RightApplicationMenu", "to": "Standard.Start" }, + { "from": "Vive.LSCenter", "to": "Standard.LeftPrimaryThumb" }, + { "from": "Vive.LeftApplicationMenu", "to": "Standard.LeftSecondaryThumb" }, + { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, + { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, { "from": "Vive.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 5cd972a38f..00a66c01cc 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -25,10 +25,13 @@ import "fileDialog" //FIXME implement shortcuts for favorite location ModalWindow { id: root - //resizable: true + resizable: true implicitWidth: 640 implicitHeight: 480 + minSize: Qt.vector2d(300, 240) + draggable: true + HifiConstants { id: hifi } Settings { diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 16f150b2d9..f2698da574 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -141,6 +141,7 @@ Item { readonly property real textPadding: 8 readonly property real sliderHandleSize: 18 readonly property real sliderGrooveHeight: 8 + readonly property real frameIconSize: 22 readonly property real spinnerSize: 50 readonly property real tablePadding: 12 readonly property real tableRowHeight: largeScreen ? 26 : 23 diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml index 04905656ce..84f435480b 100644 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ b/interface/resources/qml/windows-uit/DefaultFrame.qml @@ -20,6 +20,14 @@ Frame { Rectangle { // Dialog frame id: frameContent + + readonly property int iconSize: hifi.dimensions.frameIconSize + readonly property int frameMargin: 9 + readonly property int frameMarginLeft: frameMargin + readonly property int frameMarginRight: frameMargin + readonly property int frameMarginTop: 2 * frameMargin + iconSize + readonly property int frameMarginBottom: iconSize + 11 + anchors { topMargin: -frameMarginTop leftMargin: -frameMarginLeft @@ -34,7 +42,7 @@ Frame { } radius: hifi.dimensions.borderRadius - // Allow dragging of the window + // Enable dragging of the window MouseArea { anchors.fill: parent drag.target: window @@ -45,17 +53,17 @@ Frame { anchors { right: parent.right; top: parent.top; - topMargin: frameMargin + 1 // Move down a little to visually align with the title - rightMargin: frameMarginRight; + topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title + rightMargin: frameContent.frameMarginRight; } - spacing: iconSize / 4 + spacing: frameContent.iconSize / 4 HiFiGlyphs { // "Pin" button visible: false text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white - size: iconSize + size: frameContent.iconSize MouseArea { id: pinClickArea anchors.fill: parent @@ -70,7 +78,7 @@ Frame { visible: window ? window.closable : false text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white - size: iconSize + size: frameContent.iconSize MouseArea { id: closeClickArea anchors.fill: parent @@ -85,11 +93,11 @@ Frame { id: titleText anchors { left: parent.left - leftMargin: frameMarginLeft + hifi.dimensions.contentMargin.x + leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x right: controlsRow.left - rightMargin: iconSize + rightMargin: frameContent.iconSize top: parent.top - topMargin: frameMargin + topMargin: frameContent.frameMargin } text: window ? window.title : "" color: hifi.colors.white diff --git a/interface/resources/qml/windows-uit/Frame.qml b/interface/resources/qml/windows-uit/Frame.qml index f21097ea62..9519a44cf0 100644 --- a/interface/resources/qml/windows-uit/Frame.qml +++ b/interface/resources/qml/windows-uit/Frame.qml @@ -22,12 +22,10 @@ Item { property bool gradientsSupported: desktop.gradientsSupported - readonly property int iconSize: 22 - readonly property int frameMargin: 9 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: 2 * frameMargin + iconSize - readonly property int frameMarginBottom: iconSize + 11 + readonly property int frameMarginLeft: frameContent.frameMarginLeft + readonly property int frameMarginRight: frameContent.frameMarginRight + readonly property int frameMarginTop: frameContent.frameMarginTop + readonly property int frameMarginBottom: frameContent.frameMarginBottom // Frames always fill their parents, but their decorations may extend // beyond the window via negative margin sizes @@ -76,8 +74,8 @@ Item { id: sizeOutline x: -frameMarginLeft y: -frameMarginTop - width: window ? window.width + frameMarginLeft + frameMarginRight : 0 - height: window ? window.height + frameMarginTop + frameMarginBottom : 0 + width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 + height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 color: hifi.colors.baseGrayHighlight15 border.width: 3 border.color: hifi.colors.white50 @@ -88,11 +86,11 @@ Item { MouseArea { // Resize handle id: sizeDrag - width: iconSize - height: iconSize + width: hifi.dimensions.frameIconSize + height: hifi.dimensions.frameIconSize enabled: window ? window.resizable : false hoverEnabled: true - x: window ? window.width + frameMarginRight - iconSize : 0 + x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 y: window ? window.height + 4 : 0 property vector2d pressOrigin property vector2d sizeOrigin @@ -124,10 +122,12 @@ Item { HiFiGlyphs { visible: sizeDrag.enabled x: -11 // Move a little to visually align - y: -4 // "" + y: window.modality == Qt.ApplicationModal ? -6 : -4 text: hifi.glyphs.resizeHandle - size: iconSize + 10 - color: sizeDrag.containsMouse || sizeDrag.pressed ? hifi.colors.white : hifi.colors.white50 + size: hifi.dimensions.frameIconSize + 10 + color: sizeDrag.containsMouse || sizeDrag.pressed + ? hifi.colors.white + : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) } } } diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml index 77344829d5..44c0b6a456 100644 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ b/interface/resources/qml/windows-uit/ModalFrame.qml @@ -18,16 +18,21 @@ Frame { HifiConstants { id: hifi } Rectangle { - id: modalFrame + id: frameContent readonly property bool hasTitle: window.title != "" + readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) + readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y + anchors { fill: parent - topMargin: -hifi.dimensions.modalDialogMargin.y - (modalFrame.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) - leftMargin: -hifi.dimensions.modalDialogMargin.x - rightMargin: -hifi.dimensions.modalDialogMargin.x - bottomMargin: -hifi.dimensions.modalDialogMargin.y + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom } border { @@ -37,8 +42,15 @@ Frame { radius: hifi.dimensions.borderRadius color: hifi.colors.faintGray + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + enabled: window.draggable + } + Item { - visible: modalFrame.hasTitle + visible: frameContent.hasTitle anchors.fill: parent anchors { topMargin: -parent.anchors.topMargin diff --git a/interface/resources/qml/windows-uit/ModalWindow.qml b/interface/resources/qml/windows-uit/ModalWindow.qml index af099eb275..f429e98ac3 100644 --- a/interface/resources/qml/windows-uit/ModalWindow.qml +++ b/interface/resources/qml/windows-uit/ModalWindow.qml @@ -14,9 +14,13 @@ import "." Window { id: window - anchors.centerIn: parent modality: Qt.ApplicationModal destroyOnCloseButton: true destroyOnInvisible: true - frame: ModalFrame{} + frame: ModalFrame { } + + property int colorScheme: hifi.colorSchemes.light + property bool draggable: false + + anchors.centerIn: draggable ? undefined : parent } diff --git a/interface/resources/qml/windows-uit/Window.qml b/interface/resources/qml/windows-uit/Window.qml index e9477f3c7e..d614b21ce2 100644 --- a/interface/resources/qml/windows-uit/Window.qml +++ b/interface/resources/qml/windows-uit/Window.qml @@ -52,6 +52,7 @@ Fadable { // property bool pinned: false property bool resizable: false property bool gradientsSupported: desktop.gradientsSupported + property int colorScheme: hifi.colorSchemes.dark property vector2d minSize: Qt.vector2d(100, 100) property vector2d maxSize: Qt.vector2d(1280, 800) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7d1610c78e..48b418b93c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -630,6 +630,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + connect(&domainHandler, &DomainHandler::resetting, nodeList.data(), &NodeList::resetDomainServerCheckInVersion); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND; @@ -652,7 +654,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); - connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); + connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, this, &Application::limitOfSilentDomainCheckInsReached); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // connect to appropriate slots on AccountManager @@ -1062,6 +1064,24 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : firstRun.set(false); } +void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { + switch (static_cast(reasonCode)) { + case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: + notifyPacketVersionMismatch(); + break; + case DomainHandler::ConnectionRefusedReason::TooManyUsers: + case DomainHandler::ConnectionRefusedReason::Unknown: { + QString message = "Unable to connect to the location you are visiting.\n"; + message += reasonMessage; + OffscreenUi::warning("", message); + break; + } + default: + // nothing to do. + break; + } +} + QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; @@ -4572,6 +4592,17 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const { Physics::setSessionUUID(sessionUUID); } + +// If we're not getting anything back from the domain server checkin, it might be that the domain speaks an +// older version of the DomainConnectRequest protocol. We will attempt to send and older version of DomainConnectRequest. +// We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we +// need to be downgraded to talk to it). +void Application::limitOfSilentDomainCheckInsReached() { + auto nodeList = DependencyManager::get(); + nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version + nodeList->reset(); +} + bool Application::askToSetAvatarUrl(const QString& url) { QUrl realUrl(url); if (realUrl.isLocalFile()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 11a591776e..a17250a58e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -261,6 +261,10 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker() const; +#if (PR_BUILD || DEV_BUILD) + void sendWrongProtocolVersionsSignature(bool checked) { ::sendWrongProtocolVersionsSignature(checked); } +#endif + #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); void calibrateEyeTracker1Point(); @@ -314,6 +318,8 @@ private slots: bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; void setSessionUUID(const QUuid& sessionUUID) const; + void limitOfSilentDomainCheckInsReached(); + void domainChanged(const QString& domainHostname); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; @@ -322,6 +328,7 @@ private slots: static void packetSent(quint64 length); void updateDisplayMode(); void updateInputModes(); + void domainConnectionRefused(const QString& reasonMessage, int reason); private: static void initDisplay(); diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 83f87f82ba..24256fdf39 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -127,6 +127,10 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply if (!dataObject.isEmpty()) { _sessionID = dataObject[SESSION_ID_KEY].toString(); + + // give that session ID to the account manager + auto accountManager = DependencyManager::get(); + accountManager->setSessionID(_sessionID); } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 538410a47d..a21aa71753 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -545,6 +545,13 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); + #if (PR_BUILD || DEV_BUILD) + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, + qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); + #endif + + + // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 36d285e2cf..fcaf8e6caa 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -167,6 +167,7 @@ namespace MenuOption { const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index d12306a122..ccda77a44d 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -641,10 +641,6 @@ void Avatar::simulateAttachments(float deltaTime) { } } -void Avatar::updateJointMappings() { - // no-op; joint mappings come from skeleton model -} - float Avatar::getBoundingRadius() const { return getBounds().getLargestDimension() / 2.0f; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 7072ce3847..79952e8f58 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -236,8 +236,6 @@ protected: virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); - virtual void updateJointMappings() override; - virtual void updatePalms(); render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index babbcfadbe..6fdcd8f797 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -692,8 +692,6 @@ void MyAvatar::saveData() { settings.setValue("headPitch", getHead()->getBasePitch()); - settings.setValue("pupilDilation", getHead()->getPupilDilation()); - settings.setValue("leanScale", _leanScale); settings.setValue("scale", _targetScale); @@ -811,8 +809,6 @@ void MyAvatar::loadData() { getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); - getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f)); - _leanScale = loadSetting(settings, "leanScale", 0.05f); _targetScale = loadSetting(settings, "scale", 1.0f); setScale(glm::vec3(_targetScale)); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 0f51a484c4..dfe02a5064 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -45,7 +45,7 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reason); + void domainConnectionRefused(const QString& reasonMessage, int reasonCode); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 8f60844cc3..ce7bcc6323 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -149,11 +149,6 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { - auto getter = [=]()->float { return myAvatar->getHead()->getPupilDilation(); }; - auto setter = [=](float value) { myAvatar->getHead()->setPupilDilation(value); }; - preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Pupil dilation", getter, setter)); - } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 2d37be9b87..351c09beee 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -107,6 +107,18 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { } } +void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& rotations) const { + // poses start off absolute and leave in relative frame + int lastIndex = std::min((int)rotations.size(), (int)_joints.size()); + for (int i = lastIndex - 1; i >= 0; --i) { + int parentIndex = _joints[i].parentIndex; + if (parentIndex != -1) { + rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i]; + } + } +} + + void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const { convertRelativePosesToAbsolute(poses); mirrorAbsolutePoses(poses); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index e2cd20d63e..68cce11326 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -55,6 +55,8 @@ public: void convertRelativePosesToAbsolute(AnimPoseVec& poses) const; void convertAbsolutePosesToRelative(AnimPoseVec& poses) const; + void convertAbsoluteRotationsToRelative(std::vector& rotations) const; + void mirrorRelativePoses(AnimPoseVec& poses) const; void mirrorAbsolutePoses(AnimPoseVec& poses) const; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 67dfbec24a..9bba9ffc33 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -165,6 +165,7 @@ void Rig::destroyAnimGraph() { void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { _geometryOffset = AnimPose(geometry.offset); + _invGeometryOffset = _geometryOffset.inverse(); setModelOffset(modelOffset); _animSkeleton = std::make_shared(geometry); @@ -193,6 +194,7 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff void Rig::reset(const FBXGeometry& geometry) { _geometryOffset = AnimPose(geometry.offset); + _invGeometryOffset = _geometryOffset.inverse(); _animSkeleton = std::make_shared(geometry); _internalPoseSet._relativePoses.clear(); @@ -272,24 +274,6 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { } } -bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { - if (isIndexValid(index)) { - rotation = _internalPoseSet._relativePoses[index].rot; - return !isEqual(rotation, _animSkeleton->getRelativeDefaultPose(index).rot); - } else { - return false; - } -} - -bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { - if (isIndexValid(index)) { - translation = _internalPoseSet._relativePoses[index].trans; - return !isEqual(translation, _animSkeleton->getRelativeDefaultPose(index).trans); - } else { - return false; - } -} - void Rig::clearJointState(int index) { if (isIndexValid(index)) { _internalPoseSet._overrideFlags[index] = false; @@ -1229,24 +1213,90 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const { } void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { + + const AnimPose geometryToRigPose(_geometryToRigTransform); + jointDataVec.resize((int)getJointStateCount()); for (auto i = 0; i < jointDataVec.size(); i++) { JointData& data = jointDataVec[i]; - data.rotationSet |= getJointStateRotation(i, data.rotation); - // geometry offset is used here so that translations are in meters. - // this is what the avatar mixer expects - data.translationSet |= getJointStateTranslation(i, data.translation); - data.translation = _geometryOffset * data.translation; + if (isIndexValid(i)) { + // rotations are in absolute rig frame. + glm::quat defaultAbsRot = geometryToRigPose.rot * _animSkeleton->getAbsoluteDefaultPose(i).rot; + data.rotation = _internalPoseSet._absolutePoses[i].rot; + data.rotationSet = !isEqual(data.rotation, defaultAbsRot); + + // translations are in relative frame but scaled so that they are in meters, + // instead of geometry units. + glm::vec3 defaultRelTrans = _geometryOffset.scale * _animSkeleton->getRelativeDefaultPose(i).trans; + data.translation = _geometryOffset.scale * _internalPoseSet._relativePoses[i].trans; + data.translationSet = !isEqual(data.translation, defaultRelTrans); + } else { + data.translationSet = false; + data.rotationSet = false; + } } } void Rig::copyJointsFromJointData(const QVector& jointDataVec) { - AnimPose invGeometryOffset = _geometryOffset.inverse(); - for (int i = 0; i < jointDataVec.size(); i++) { - const JointData& data = jointDataVec.at(i); - setJointRotation(i, data.rotationSet, data.rotation, 1.0f); - // geometry offset is used here to undo the fact that avatar mixer translations are in meters. - setJointTranslation(i, data.translationSet, invGeometryOffset * data.translation, 1.0f); + + if (_animSkeleton) { + + // transform all the default poses into rig space. + const AnimPose geometryToRigPose(_geometryToRigTransform); + std::vector overrideFlags(_internalPoseSet._overridePoses.size(), false); + + // start with the default rotations in absolute rig frame + std::vector rotations; + rotations.reserve(_animSkeleton->getAbsoluteDefaultPoses().size()); + for (auto& pose : _animSkeleton->getAbsoluteDefaultPoses()) { + rotations.push_back(geometryToRigPose.rot * pose.rot); + } + + // start translations in relative frame but scaled to meters. + std::vector translations; + translations.reserve(_animSkeleton->getRelativeDefaultPoses().size()); + for (auto& pose : _animSkeleton->getRelativeDefaultPoses()) { + translations.push_back(_geometryOffset.scale * pose.trans); + } + + ASSERT(overrideFlags.size() == rotations.size()); + + // copy over rotations from the jointDataVec, which is also in absolute rig frame + for (int i = 0; i < jointDataVec.size(); i++) { + if (isIndexValid(i)) { + const JointData& data = jointDataVec.at(i); + if (data.rotationSet) { + overrideFlags[i] = true; + rotations[i] = data.rotation; + } + if (data.translationSet) { + overrideFlags[i] = true; + translations[i] = data.translation; + } + } + } + + ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + + // convert resulting rotations into geometry space. + const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); + for (auto& rot : rotations) { + rot = rigToGeometryRot * rot; + } + + // convert all rotations from absolute to parent relative. + _animSkeleton->convertAbsoluteRotationsToRelative(rotations); + + // copy the geometry space parent relative poses into _overridePoses + for (int i = 0; i < jointDataVec.size(); i++) { + if (overrideFlags[i]) { + _internalPoseSet._overrideFlags[i] = true; + _internalPoseSet._overridePoses[i].scale = Vectors::ONE; + _internalPoseSet._overridePoses[i].rot = rotations[i]; + // scale translations from meters back into geometry units. + _internalPoseSet._overridePoses[i].trans = _invGeometryOffset.scale * translations[i]; + } + } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 363006d48c..891d9fdb92 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -104,12 +104,6 @@ public: void setModelOffset(const glm::mat4& modelOffsetMat); - // geometry space - bool getJointStateRotation(int index, glm::quat& rotation) const; - - // geometry space - bool getJointStateTranslation(int index, glm::vec3& translation) const; - void clearJointState(int index); void clearJointStates(); void clearJointAnimationPriority(int index); @@ -119,8 +113,6 @@ public: // geometry space void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); - - // geometry space void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); // legacy @@ -239,6 +231,7 @@ protected: AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) + AnimPose _invGeometryOffset; struct PoseSet { AnimPoseVec _relativePoses; // geometry space relative to parent. diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b26cecbc9e..16e4bd5437 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -37,6 +37,8 @@ #include "AvatarLogging.h" +//#define WANT_DEBUG + quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; using namespace std; @@ -46,6 +48,52 @@ const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f); const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; +namespace AvatarDataPacket { + // NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure. + + PACKED_BEGIN struct Header { + float position[3]; // skeletal model's position + float globalPosition[3]; // avatar's position + uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to + uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag. + float lookAtPosition[3]; // world space position that eyes are focusing on. + float audioLoudness; // current loundess of microphone + uint8_t flags; + } PACKED_END; + const size_t HEADER_SIZE = 49; + + // only present if HAS_REFERENTIAL flag is set in header.flags + PACKED_BEGIN struct ParentInfo { + uint8_t parentUUID[16]; // rfc 4122 encoded + uint16_t parentJointIndex; + } PACKED_END; + const size_t PARENT_INFO_SIZE = 18; + + // only present if IS_FACESHIFT_CONNECTED flag is set in header.flags + PACKED_BEGIN struct FaceTrackerInfo { + float leftEyeBlink; + float rightEyeBlink; + float averageLoudness; + float browAudioLift; + uint8_t numBlendshapeCoefficients; + // float blendshapeCoefficients[numBlendshapeCoefficients]; + } PACKED_END; + const size_t FACE_TRACKER_INFO_SIZE = 17; + + // variable length structure follows + /* + struct JointData { + uint8_t numJoints; + uint8_t rotationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed rotation follows. + SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes() + uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows. + SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed() + }; + */ +} + +#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0) + AvatarData::AvatarData() : SpatiallyNestable(NestableType::Avatar, QUuid()), _handPosition(0.0f), @@ -66,6 +114,10 @@ AvatarData::AvatarData() : setBodyPitch(0.0f); setBodyYaw(-90.0f); setBodyRoll(0.0f); + + ASSERT(sizeof(AvatarDataPacket::Header) == AvatarDataPacket::HEADER_SIZE); + ASSERT(sizeof(AvatarDataPacket::ParentInfo) == AvatarDataPacket::PARENT_INFO_SIZE); + ASSERT(sizeof(AvatarDataPacket::FaceTrackerInfo) == AvatarDataPacket::FACE_TRACKER_INFO_SIZE); } AvatarData::~AvatarData() { @@ -139,87 +191,70 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - const glm::vec3& position = getLocalPosition(); - memcpy(destinationBuffer, &position, sizeof(position)); - destinationBuffer += sizeof(position); + auto header = reinterpret_cast(destinationBuffer); + header->position[0] = getLocalPosition().x; + header->position[1] = getLocalPosition().y; + header->position[2] = getLocalPosition().z; + header->globalPosition[0] = _globalPosition.x; + header->globalPosition[1] = _globalPosition.y; + header->globalPosition[2] = _globalPosition.z; - memcpy(destinationBuffer, &_globalPosition, sizeof(_globalPosition)); - destinationBuffer += sizeof(_globalPosition); - - // Body rotation glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation())); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.y); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.x); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.z); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 1), bodyEulerAngles.x); + packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 2), bodyEulerAngles.z); + packFloatRatioToTwoByte((uint8_t*)(&header->scale), _targetScale); + header->lookAtPosition[0] = _headData->_lookAtPosition.x; + header->lookAtPosition[1] = _headData->_lookAtPosition.y; + header->lookAtPosition[2] = _headData->_lookAtPosition.z; + header->audioLoudness = _headData->_audioLoudness; - // Body scale - destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); - - // Lookat Position - memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition)); - destinationBuffer += sizeof(_headData->_lookAtPosition); - - // Instantaneous audio loudness (used to drive facial animation) - memcpy(destinationBuffer, &_headData->_audioLoudness, sizeof(float)); - destinationBuffer += sizeof(float); - - // bitMask of less than byte wide items - unsigned char bitItems = 0; - - // key state - setSemiNibbleAt(bitItems,KEY_STATE_START_BIT,_keyState); + setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState); // hand state bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; - setSemiNibbleAt(bitItems, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); + setSemiNibbleAt(header->flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); if (isFingerPointing) { - setAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT); + setAtBit(header->flags, HAND_STATE_FINGER_POINTING_BIT); } // faceshift state if (_headData->_isFaceTrackerConnected) { - setAtBit(bitItems, IS_FACESHIFT_CONNECTED); + setAtBit(header->flags, IS_FACESHIFT_CONNECTED); } // eye tracker state if (_headData->_isEyeTrackerConnected) { - setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); + setAtBit(header->flags, IS_EYE_TRACKER_CONNECTED); } // referential state QUuid parentID = getParentID(); if (!parentID.isNull()) { - setAtBit(bitItems, HAS_REFERENTIAL); + setAtBit(header->flags, HAS_REFERENTIAL); } - *destinationBuffer++ = bitItems; + destinationBuffer += sizeof(AvatarDataPacket::Header); if (!parentID.isNull()) { + auto parentInfo = reinterpret_cast(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); - memcpy(destinationBuffer, referentialAsBytes.data(), referentialAsBytes.size()); - destinationBuffer += referentialAsBytes.size(); - memcpy(destinationBuffer, &_parentJointIndex, sizeof(_parentJointIndex)); - destinationBuffer += sizeof(_parentJointIndex); + memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); + parentInfo->parentJointIndex = _parentJointIndex; + destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); } // If it is connected, pack up the data if (_headData->_isFaceTrackerConnected) { - memcpy(destinationBuffer, &_headData->_leftEyeBlink, sizeof(float)); - destinationBuffer += sizeof(float); + auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - memcpy(destinationBuffer, &_headData->_rightEyeBlink, sizeof(float)); - destinationBuffer += sizeof(float); + faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; + faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; + faceTrackerInfo->averageLoudness = _headData->_averageLoudness; + faceTrackerInfo->browAudioLift = _headData->_browAudioLift; + faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); + destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - memcpy(destinationBuffer, &_headData->_averageLoudness, sizeof(float)); - destinationBuffer += sizeof(float); - - memcpy(destinationBuffer, &_headData->_browAudioLift, sizeof(float)); - destinationBuffer += sizeof(float); - - *destinationBuffer++ = _headData->_blendshapeCoefficients.size(); - memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), - _headData->_blendshapeCoefficients.size() * sizeof(float)); + // followed by a variable number of float coefficients + memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); } - // pupil dilation - destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f); - // joint rotation data *destinationBuffer++ = _jointData.size(); unsigned char* validityPosition = destinationBuffer; @@ -261,7 +296,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { for (int i = 0; i < _jointData.size(); i ++) { const JointData& data = _jointData[ i ]; if (validity & (1 << validityBit)) { - destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); } if (++validityBit == BITS_IN_BYTE) { validityBit = 0; @@ -304,15 +339,11 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } - if (validityBit != 0) { *destinationBuffer++ = validity; } - // TODO -- automatically pick translationCompressionRadix - int translationCompressionRadix = 12; - - *destinationBuffer++ = translationCompressionRadix; + const int TRANSLATION_COMPRESSION_RADIX = 12; validityBit = 0; validity = *validityPosition++; @@ -320,7 +351,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { const JointData& data = _jointData[ i ]; if (validity & (1 << validityBit)) { destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, translationCompressionRadix); + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); } if (++validityBit == BITS_IN_BYTE) { validityBit = 0; @@ -333,7 +364,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll << "rotations:" << rotationSentCount << "translations:" << translationSentCount << "largest:" << maxTranslationDimension - << "radix:" << translationCompressionRadix << "size:" << (beforeRotations - startPosition) << "+" << (beforeTranslations - beforeRotations) << "+" @@ -370,6 +400,12 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { } bool AvatarData::shouldLogError(const quint64& now) { +#ifdef WANT_DEBUG + if (now > 0) { + return true; + } +#endif + if (now > _errorLogExpiry) { _errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; return true; @@ -377,6 +413,16 @@ bool AvatarData::shouldLogError(const quint64& now) { return false; } +#define PACKET_READ_CHECK(ITEM_NAME, SIZE_TO_READ) \ + if ((endPosition - sourceBuffer) < (int)SIZE_TO_READ) { \ + if (shouldLogError(now)) { \ + qCWarning(avatars) << "AvatarData packet too small, attempting to read " << \ + #ITEM_NAME << ", only " << (endPosition - sourceBuffer) << \ + " bytes left, " << getSessionUUID(); \ + } \ + return buffer.size(); \ + } + // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { @@ -386,125 +432,76 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } const unsigned char* startPosition = reinterpret_cast(buffer.data()); + const unsigned char* endPosition = startPosition + buffer.size(); const unsigned char* sourceBuffer = startPosition; quint64 now = usecTimestampNow(); - // The absolute minimum size of the update data is as follows: - // 36 bytes of "plain old data" { - // position = 12 bytes - // bodyYaw = 2 (compressed float) - // bodyPitch = 2 (compressed float) - // bodyRoll = 2 (compressed float) - // targetScale = 2 (compressed float) - // lookAt = 12 - // audioLoudness = 4 - // } - // + 1 byte for varying data - // + 1 byte for pupilSize - // + 1 byte for numJoints (0) - // = 39 bytes - int minPossibleSize = 39; + PACKET_READ_CHECK(Header, sizeof(AvatarDataPacket::Header)); + auto header = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::Header); - int maxAvailableSize = buffer.size(); - if (minPossibleSize > maxAvailableSize) { + glm::vec3 position = glm::vec3(header->position[0], header->position[1], header->position[2]); + _globalPosition = glm::vec3(header->globalPosition[0], header->globalPosition[1], header->globalPosition[2]); + if (isNaN(position)) { if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet at the start; " - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; + qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID(); } - // this packet is malformed so we report all bytes as consumed - return maxAvailableSize; + return buffer.size(); + } + setLocalPosition(position); + + float pitch, yaw, roll; + unpackFloatAngleFromTwoByte(header->localOrientation + 0, &yaw); + unpackFloatAngleFromTwoByte(header->localOrientation + 1, &pitch); + unpackFloatAngleFromTwoByte(header->localOrientation + 2, &roll); + if (isNaN(yaw) || isNaN(pitch) || isNaN(roll)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: localOriention is NaN, uuid " << getSessionUUID(); + } + return buffer.size(); } - { // Body world position, rotation, and scale - // position - glm::vec3 position; - memcpy(&position, sourceBuffer, sizeof(position)); - sourceBuffer += sizeof(position); + glm::quat currentOrientation = getLocalOrientation(); + glm::vec3 newEulerAngles(pitch, yaw, roll); + glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); + if (currentOrientation != newOrientation) { + _hasNewJointRotations = true; + setLocalOrientation(newOrientation); + } - memcpy(&_globalPosition, sourceBuffer, sizeof(_globalPosition)); - sourceBuffer += sizeof(_globalPosition); - - if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::position; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; + float scale; + unpackFloatRatioFromTwoByte((uint8_t*)&header->scale, scale); + if (isNaN(scale)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: scale NaN, uuid " << getSessionUUID(); } - setLocalPosition(position); + return buffer.size(); + } + _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); - // rotation (NOTE: This needs to become a quaternion to save two bytes) - float yaw, pitch, roll; - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &yaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &pitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &roll); - if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::yaw,pitch,roll; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; + glm::vec3 lookAt = glm::vec3(header->lookAtPosition[0], header->lookAtPosition[1], header->lookAtPosition[2]); + if (isNaN(lookAt)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: lookAtPosition is NaN, uuid " << getSessionUUID(); } + return buffer.size(); + } + _headData->_lookAtPosition = lookAt; - // TODO is this safe? will the floats not exactly match? - // Andrew says: - // Yes, there is a possibility that the transmitted will not quite match the extracted despite being originally - // extracted from the exact same quaternion. I followed the code through and it appears the risk is that the - // avatar's SkeletonModel might fall into the CPU expensive part of Model::updateClusterMatrices() when otherwise it - // would not have required it. However, we know we can update many simultaneously animating avatars, and most - // avatars will be moving constantly anyway, so I don't think we need to worry. - glm::quat currentOrientation = getLocalOrientation(); - glm::vec3 newEulerAngles(pitch, yaw, roll); - glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); - if (currentOrientation != newOrientation) { - _hasNewJointRotations = true; - setLocalOrientation(newOrientation); + float audioLoudness = header->audioLoudness; + if (isNaN(audioLoudness)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: audioLoudness is NaN, uuid " << getSessionUUID(); } - - // scale - float scale; - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, scale); - if (glm::isnan(scale)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::scale; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); - } // 20 bytes - - { // Lookat Position - glm::vec3 lookAt; - memcpy(&lookAt, sourceBuffer, sizeof(lookAt)); - sourceBuffer += sizeof(lookAt); - if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::lookAt; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_lookAtPosition = lookAt; - } // 12 bytes - - { // AudioLoudness - // Instantaneous audio loudness (used to drive facial animation) - float audioLoudness; - memcpy(&audioLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - if (glm::isnan(audioLoudness)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::audioLoudness; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_audioLoudness = audioLoudness; - } // 4 bytes + return buffer.size(); + } + _headData->_audioLoudness = audioLoudness; { // bitFlags and face data - unsigned char bitItems = *sourceBuffer++; + uint8_t bitItems = header->flags; // key state, stored as a semi-nibble in the bitItems - _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); + _keyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT); // hand state, stored as a semi-nibble plus a bit in the bitItems // we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split @@ -521,98 +518,47 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL); if (hasReferential) { - const int sizeOfPackedUuid = 16; - QByteArray referentialAsBytes((const char*)sourceBuffer, sizeOfPackedUuid); - _parentID = QUuid::fromRfc4122(referentialAsBytes); - sourceBuffer += sizeOfPackedUuid; - memcpy(&_parentJointIndex, sourceBuffer, sizeof(_parentJointIndex)); - sourceBuffer += sizeof(_parentJointIndex); + PACKET_READ_CHECK(ParentInfo, sizeof(AvatarDataPacket::ParentInfo)); + auto parentInfo = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::ParentInfo); + + QByteArray byteArray((const char*)parentInfo->parentUUID, NUM_BYTES_RFC4122_UUID); + _parentID = QUuid::fromRfc4122(byteArray); + _parentJointIndex = parentInfo->parentJointIndex; } else { _parentID = QUuid(); } if (_headData->_isFaceTrackerConnected) { - float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift; - minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift); - minPossibleSize++; // one byte for blendDataSize - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after BitItems;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - // unpack face data - memcpy(&leftEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); + PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo)); + auto faceTrackerInfo = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - memcpy(&rightEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); + _headData->_leftEyeBlink = faceTrackerInfo->leftEyeBlink; + _headData->_rightEyeBlink = faceTrackerInfo->rightEyeBlink; + _headData->_averageLoudness = faceTrackerInfo->averageLoudness; + _headData->_browAudioLift = faceTrackerInfo->browAudioLift; - memcpy(&averageLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - memcpy(&browAudioLift, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - if (glm::isnan(leftEyeBlink) || glm::isnan(rightEyeBlink) - || glm::isnan(averageLoudness) || glm::isnan(browAudioLift)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::faceData; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_leftEyeBlink = leftEyeBlink; - _headData->_rightEyeBlink = rightEyeBlink; - _headData->_averageLoudness = averageLoudness; - _headData->_browAudioLift = browAudioLift; - - int numCoefficients = (int)(*sourceBuffer++); - int blendDataSize = numCoefficients * sizeof(float); - minPossibleSize += blendDataSize; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after Blendshapes;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - - _headData->_blendshapeCoefficients.resize(numCoefficients); - memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, blendDataSize); - sourceBuffer += numCoefficients * sizeof(float); - - //bitItemsDataSize = 4 * sizeof(float) + 1 + blendDataSize; + int numCoefficients = faceTrackerInfo->numBlendshapeCoefficients; + const int coefficientsSize = sizeof(float) * numCoefficients; + PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize); + _headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy! + memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize); + sourceBuffer += coefficientsSize; } - } // 1 + bitItemsDataSize bytes - - { // pupil dilation - sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); - } // 1 byte - - // joint rotations - int numJoints = *sourceBuffer++; - int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); - minPossibleSize += bytesOfValidity; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointValidityBits;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; } - int numValidJointRotations = 0; + + PACKET_READ_CHECK(NumJoints, sizeof(uint8_t)); + int numJoints = *sourceBuffer++; + _jointData.resize(numJoints); + const int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); + PACKET_READ_CHECK(JointRotationValidityBits, bytesOfValidity); + + int numValidJointRotations = 0; QVector validRotations; validRotations.resize(numJoints); - { // rotation validity bits unsigned char validity = 0; int validityBit = 0; @@ -627,38 +573,26 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { validRotations[i] = valid; validityBit = (validityBit + 1) % BITS_IN_BYTE; } - } // 1 + bytesOfValidity bytes - - // each joint rotation component is stored in two bytes (sizeof(uint16_t)) - int COMPONENTS_PER_QUATERNION = 4; - minPossibleSize += numValidJointRotations * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; } - { // joint data - for (int i = 0; i < numJoints; i++) { - JointData& data = _jointData[i]; - if (validRotations[i]) { - _hasNewJointRotations = true; - data.rotationSet = true; - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); - } + // each joint rotation is stored in 6 bytes. + const int COMPRESSED_QUATERNION_SIZE = 6; + PACKET_READ_CHECK(JointRotations, numValidJointRotations * COMPRESSED_QUATERNION_SIZE); + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validRotations[i]) { + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); + _hasNewJointRotations = true; + data.rotationSet = true; } - } // numJoints * 8 bytes + } + + PACKET_READ_CHECK(JointTranslationValidityBits, bytesOfValidity); - // joint translations // get translation validity bits -- these indicate which translations were packed int numValidJointTranslations = 0; QVector validTranslations; validTranslations.resize(numJoints); - { // translation validity bits unsigned char validity = 0; int validityBit = 0; @@ -675,42 +609,36 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } // 1 + bytesOfValidity bytes - // each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix - minPossibleSize += numValidJointTranslations * 6 + 1; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; + // each joint translation component is stored in 6 bytes. + const int COMPRESSED_TRANSLATION_SIZE = 6; + PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE); + const int TRANSLATION_COMPRESSION_RADIX = 12; + + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validTranslations[i]) { + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + _hasNewJointTranslations = true; + data.translationSet = true; } - return maxAvailableSize; } - int translationCompressionRadix = *sourceBuffer++; - - { // joint data - for (int i = 0; i < numJoints; i++) { - JointData& data = _jointData[i]; - if (validTranslations[i]) { - sourceBuffer += - unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); - _hasNewJointTranslations = true; - data.translationSet = true; - } - } - } // numJoints * 12 bytes - #ifdef WANT_DEBUG if (numValidJointRotations > 15) { qDebug() << "RECEIVING -- rotations:" << numValidJointRotations << "translations:" << numValidJointTranslations - << "radix:" << translationCompressionRadix << "size:" << (int)(sourceBuffer - startPosition); } #endif int numBytesRead = sourceBuffer - startPosition; + + if (numBytesRead != buffer.size()) { + if (shouldLogError(now)) { + qCWarning(avatars) << "AvatarData packet size mismatch: expected " << numBytesRead << " received " << buffer.size(); + } + } + _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; } @@ -954,38 +882,33 @@ void AvatarData::clearJointsData() { } } -bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { - // this is used by the avatar-mixer +void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); - QUuid avatarUUID; - QUrl unusedModelURL; // legacy faceModel support - QUrl skeletonModelURL; - QVector attachmentData; - AvatarEntityMap avatarEntityData; - QString displayName; - packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName >> avatarEntityData; + packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.avatarEntityData; +} +bool AvatarData::processAvatarIdentity(const Identity& identity) { bool hasIdentityChanged = false; - if (_firstSkeletonCheck || (skeletonModelURL != _skeletonModelURL)) { - setSkeletonModelURL(skeletonModelURL); + if (_firstSkeletonCheck || (identity.skeletonModelURL != _skeletonModelURL)) { + setSkeletonModelURL(identity.skeletonModelURL); hasIdentityChanged = true; _firstSkeletonCheck = false; } - if (displayName != _displayName) { - setDisplayName(displayName); + if (identity.displayName != _displayName) { + setDisplayName(identity.displayName); hasIdentityChanged = true; } - if (attachmentData != _attachmentData) { - setAttachmentData(attachmentData); + if (identity.attachmentData != _attachmentData) { + setAttachmentData(identity.attachmentData); hasIdentityChanged = true; } - if (avatarEntityData != _avatarEntityData) { - setAvatarEntityData(avatarEntityData); + if (identity.avatarEntityData != _avatarEntityData) { + setAvatarEntityData(identity.avatarEntityData); hasIdentityChanged = true; } @@ -998,21 +921,18 @@ QByteArray AvatarData::identityByteArray() { QUrl emptyURL(""); const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; - QUrl unusedModelURL; // legacy faceModel support - - identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName << _avatarEntityData; + identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << _avatarEntityData; return identityData; } - void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; if (expanded == _skeletonModelURL) { return; } _skeletonModelURL = expanded; - qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); + qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); updateJointMappings(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2402a052c6..61ee649273 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -53,6 +53,7 @@ typedef unsigned long long quint64; #include #include #include +#include #include "AABox.h" #include "HeadData.h" @@ -171,6 +172,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) public: + static const QString FRAME_NAME; static void fromFrame(const QByteArray& frameData, AvatarData& avatar); @@ -289,7 +291,19 @@ public: const HeadData* getHeadData() const { return _headData; } - bool hasIdentityChangedAfterParsing(const QByteArray& data); + struct Identity { + QUuid uuid; + QUrl skeletonModelURL; + QVector attachmentData; + QString displayName; + AvatarEntityMap avatarEntityData; + }; + + static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); + + // returns true if identity has changed, false otherwise. + bool processAvatarIdentity(const Identity& identity); + QByteArray identityByteArray(); const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index bd43560ae8..9084fd837b 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -50,26 +50,26 @@ AvatarSharedPointer AvatarHashMap::newSharedAvatar() { AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - + auto avatar = newSharedAvatar(); avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); - + _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); - + return avatar; } AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { QWriteLocker locker(&_hashLock); - + auto avatar = _avatarHash.value(sessionUUID); - + if (!avatar) { avatar = addAvatar(sessionUUID, mixerWeakPointer); } - + return avatar; } @@ -86,14 +86,14 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (message->getBytesLeftToRead()) { QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - + int positionBeforeRead = message->getPosition(); QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); - + if (sessionUUID != _lastOwnerSessionUUID) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - + // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); @@ -107,37 +107,12 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { - // this is used by clients - // setup a data stream to parse the packet - QDataStream identityStream(message->getMessage()); + AvatarData::Identity identity; + AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); - QUuid sessionUUID; - - while (!identityStream.atEnd()) { - - QUrl faceMeshURL, skeletonURL; - QVector attachmentData; - AvatarEntityMap avatarEntityData; - QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName >> avatarEntityData; - - // mesh URL for a UUID, find avatar in our list - auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - - if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) { - avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire - } - - if (avatar->getAttachmentData() != attachmentData) { - avatar->setAttachmentData(attachmentData); - } - - avatar->setAvatarEntityData(avatarEntityData); - - if (avatar->getDisplayName() != displayName) { - avatar->setDisplayName(displayName); - } - } + // mesh URL for a UUID, find avatar in our list + auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); + avatar->processAvatarIdentity(identity); } void AvatarHashMap::processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode) { @@ -148,9 +123,9 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) { QWriteLocker locker(&_hashLock); - + auto removedAvatar = _avatarHash.take(sessionUUID); - + if (removedAvatar) { handleRemovedAvatar(removedAvatar); } diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index b98112d6e0..1aee85b2cd 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -43,10 +43,9 @@ HeadData::HeadData(AvatarData* owningAvatar) : _averageLoudness(0.0f), _browAudioLift(0.0f), _audioAverageLoudness(0.0f), - _pupilDilation(0.0f), _owningAvatar(owningAvatar) { - + } glm::quat HeadData::getRawOrientation() const { @@ -72,7 +71,7 @@ void HeadData::setOrientation(const glm::quat& orientation) { glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT); bodyOrientation = bodyOrientation * glm::angleAxis(atan2f(-newFront.x, -newFront.z), glm::vec3(0.0f, 1.0f, 0.0f)); _owningAvatar->setOrientation(bodyOrientation); - + // the rest goes to the head glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation)); _basePitch = eulers.x; @@ -186,4 +185,3 @@ void HeadData::fromJson(const QJsonObject& json) { } } } - diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index fef77c6f8f..535aa12847 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -34,7 +34,7 @@ class HeadData { public: explicit HeadData(AvatarData* owningAvatar); virtual ~HeadData() { }; - + // degrees float getBaseYaw() const { return _baseYaw; } void setBaseYaw(float yaw) { _baseYaw = glm::clamp(yaw, MIN_HEAD_YAW, MAX_HEAD_YAW); } @@ -42,7 +42,7 @@ public: void setBasePitch(float pitch) { _basePitch = glm::clamp(pitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH); } float getBaseRoll() const { return _baseRoll; } void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } - + virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; } virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; } virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; } @@ -64,26 +64,23 @@ public: void setBlendshape(QString name, float val); const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } - - float getPupilDilation() const { return _pupilDilation; } - void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; } - + const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } - - + + float getLeanSideways() const { return _leanSideways; } float getLeanForward() const { return _leanForward; } float getTorsoTwist() const { return _torsoTwist; } virtual float getFinalLeanSideways() const { return _leanSideways; } virtual float getFinalLeanForward() const { return _leanForward; } - + void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } void setLeanForward(float leanForward) { _leanForward = leanForward; } void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; } - + friend class AvatarData; - + QJsonObject toJson() const; void fromJson(const QJsonObject& json); @@ -106,9 +103,8 @@ protected: float _browAudioLift; float _audioAverageLoudness; QVector _blendshapeCoefficients; - float _pupilDilation; AvatarData* _owningAvatar; - + private: // privatize copy ctor and assignment operator so copies of this object cannot be made HeadData(const HeadData&); diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index f101ba6c51..79c23bc6ee 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -43,6 +43,8 @@ namespace controller { LEFT_SECONDARY_THUMB_TOUCH, LS_TOUCH, LEFT_THUMB_UP, + LS_CENTER, + LS_OUTER, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -50,6 +52,8 @@ namespace controller { RIGHT_SECONDARY_THUMB_TOUCH, RS_TOUCH, RIGHT_THUMB_UP, + RS_CENTER, + RS_OUTER, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 2527dedab2..84208cc6f1 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -155,13 +155,13 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); - APPEND_ENTITY_PROPERTY(PROP_COLOR, getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); } // This value specifes how the shape should be treated by physics calculations. // For now, all polys will act as spheres ShapeType ShapeEntityItem::getShapeType() const { - return SHAPE_TYPE_ELLIPSOID; + return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_SPHERE; } void ShapeEntityItem::setColor(const rgbColor& value) { diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 7cc4691d63..7cb745ff53 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -98,10 +98,18 @@ float getLightShowContour(Light l) { return l._control.w; } +// Light is the light source its self, d is the light's distance calculated as length(unnormalized light vector). float evalLightAttenuation(Light l, float d) { float radius = getLightRadius(l); float denom = d / radius + 1.0; - float attenuation = min(1.0, 1.0 / (denom * denom)); + float attenuation = 1.0 / (denom * denom); + + float cutoff = getLightCutoffRadius(l); + + // "Fade" the edges of light sources to make things look a bit more attractive. + // Note: this tends to look a bit odd at lower exponents. + attenuation *= min(1, max(0, -(d - cutoff))); + return attenuation; } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 9080e3cc53..46e72170e5 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AccountManager.h" + #include #include @@ -26,13 +28,13 @@ #include +#include "NetworkLogging.h" #include "NodeList.h" #include "udt/PacketHeaders.h" #include "RSAKeypairGenerator.h" #include "SharedUtil.h" +#include "UserActivityLogger.h" -#include "AccountManager.h" -#include "NetworkLogging.h" const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; @@ -216,6 +218,13 @@ void AccountManager::sendRequest(const QString& path, networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + // if we're allowed to send usage data, include whatever the current session ID is with this request + auto& activityLogger = UserActivityLogger::getInstance(); + if (activityLogger.isEnabled()) { + static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID"; + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(), _sessionID.toString().toLocal8Bit()); + } + QUrl requestURL = _authURL; if (path.startsWith("/")) { diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 89a2240bbb..4803d2625f 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -86,6 +86,8 @@ public: static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } + public slots: void requestAccessToken(const QString& login, const QString& password); @@ -136,6 +138,8 @@ private: bool _isWaitingForKeypairResponse { false }; QByteArray _pendingPrivateKey; + + QUuid _sessionID; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 619a1d7903..80989acd2c 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -383,8 +383,12 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) { qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); if (errorReply.error() == QNetworkReply::ContentNotFoundError) { + // if this is a lookup that has no result, don't keep re-trying it + _previousLookup.clear(); + emit lookupResultIsNotFound(); } + emit lookupResultsFinished(); } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b3c3a28829..1efcfc7f27 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -103,7 +103,6 @@ void DomainHandler::hardReset() { _sockAddr.clear(); _hasCheckedForAccessToken = false; - _domainConnectionRefusals.clear(); // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); @@ -142,6 +141,9 @@ void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const // set the new hostname _hostname = hostname; + // FIXME - is this the right place??? + _domainConnectionRefusals.clear(); + qCDebug(networking) << "Updated domain hostname to" << _hostname; // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname @@ -349,34 +351,58 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes } } +bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { + switch (reasonCode) { + case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::NotAuthorized: + return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; + } + return false; +} + void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { // Read deny reason from packet + uint8_t reasonCodeWire; + + message->readPrimitive(&reasonCodeWire); + ConnectionRefusedReason reasonCode = static_cast(reasonCodeWire); quint16 reasonSize; message->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + auto reasonText = message->readWithoutCopy(reasonSize); + QString reasonMessage = QString::fromUtf8(reasonText); // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in - qCWarning(networking) << "The domain-server denied a connection request: " << reason; - qCWarning(networking) << "Make sure you are logged in."; + qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; - if (!_domainConnectionRefusals.contains(reason)) { - _domainConnectionRefusals.append(reason); - emit domainConnectionRefused(reason); + if (!_domainConnectionRefusals.contains(reasonMessage)) { + _domainConnectionRefusals.append(reasonMessage); + emit domainConnectionRefused(reasonMessage, (int)reasonCode); } auto accountManager = DependencyManager::get(); - if (!_hasCheckedForAccessToken) { - accountManager->checkAndSignalForAccessToken(); - _hasCheckedForAccessToken = true; - } + // Some connection refusal reasons imply that a login is required. If so, suggest a new login + if (reasonSuggestsLogin(reasonCode)) { + qCWarning(networking) << "Make sure you are logged in."; - static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + if (!_hasCheckedForAccessToken) { + accountManager->checkAndSignalForAccessToken(); + _hasCheckedForAccessToken = true; + } - // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts - if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { - accountManager->generateNewUserKeypair(); - _connectionDenialsSinceKeypairRegen = 0; + static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + + // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts + if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { + accountManager->generateNewUserKeypair(); + _connectionDenialsSinceKeypairRegen = 0; + } } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index c6269191d2..226186f1d0 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -84,6 +84,15 @@ public: bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); } void softReset(); + + enum class ConnectionRefusedReason : uint8_t { + Unknown, + ProtocolMismatch, + LoginError, + NotAuthorized, + TooManyUsers + }; + public slots: void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid()); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); @@ -115,9 +124,10 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); - void domainConnectionRefused(QString reason); + void domainConnectionRefused(QString reasonMessage, int reason); private: + bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); void sendDisconnectPacket(); void hardReset(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2c10d0627e..9efe51183e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -176,9 +176,10 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { bool hasBeenOutput = false; QString senderString; + const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); + QUuid sourceID; if (NON_SOURCED_PACKETS.contains(headerType)) { - const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, headerType); if (!hasBeenOutput) { @@ -186,7 +187,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort()); } } else { - QUuid sourceID = NLPacket::sourceIDInHeader(packet); + sourceID = NLPacket::sourceIDInHeader(packet); hasBeenOutput = sourcedVersionDebugSuppressMap.contains(sourceID, headerType); @@ -201,7 +202,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { << senderString << "sent" << qPrintable(QString::number(headerVersion)) << "but" << qPrintable(QString::number(versionForPacketType(headerType))) << "expected."; - emit packetVersionMismatch(headerType); + emit packetVersionMismatch(headerType, senderSockAddr, sourceID); } return false; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 0cbe9668b3..5a3c10e8c3 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -221,6 +221,10 @@ public: void setConnectionMaxBandwidth(int maxBandwidth) { _nodeSocket.setConnectionMaxBandwidth(maxBandwidth); } + void setPacketFilterOperator(udt::PacketFilterOperator filterOperator) { _nodeSocket.setPacketFilterOperator(filterOperator); } + bool packetVersionMatch(const udt::Packet& packet); + bool isPacketVerified(const udt::Packet& packet); + public slots: void reset(); void eraseAllNodes(); @@ -236,7 +240,9 @@ public slots: signals: void dataSent(quint8 channelType, int bytes); - void packetVersionMismatch(PacketType type); + + // QUuid might be zero for non-sourced packet types. + void packetVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); @@ -267,8 +273,6 @@ protected: void setLocalSocket(const HifiSockAddr& sockAddr); - bool isPacketVerified(const udt::Packet& packet); - bool packetVersionMatch(const udt::Packet& packet); bool packetSourceAndHashMatch(const udt::Packet& packet); void processSTUNResponse(std::unique_ptr packet); diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 575a2c7a9c..34a159ae6c 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -24,8 +24,8 @@ int NLPacket::maxPayloadSize(PacketType type, bool isPartOfMessage) { return Packet::maxPayloadSize(isPartOfMessage) - NLPacket::localHeaderSize(type); } -std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) { - auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage)); +std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) { + auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage, version)); packet->open(QIODevice::ReadWrite); @@ -61,13 +61,13 @@ std::unique_ptr NLPacket::createCopy(const NLPacket& other) { return std::unique_ptr(new NLPacket(other)); } -NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) : +NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) : Packet((size == -1) ? -1 : NLPacket::localHeaderSize(type) + size, isReliable, isPartOfMessage), _type(type), - _version(versionForPacketType(type)) + _version((version == 0) ? versionForPacketType(type) : version) { adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type)); - + writeTypeAndVersion(); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index 4527094322..f49f8498a5 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -38,7 +38,7 @@ public: sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; static std::unique_ptr create(PacketType type, qint64 size = -1, - bool isReliable = false, bool isPartOfMessage = false); + bool isReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); @@ -73,7 +73,7 @@ public: protected: - NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false); + NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); NLPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); NLPacket(const NLPacket& other); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index a5fff36be8..082200fccc 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -292,7 +292,8 @@ void NodeList::sendDomainServerCheckIn() { return; } - auto domainPacket = NLPacket::create(domainPacketType); + auto packetVersion = (domainPacketType == PacketType::DomainConnectRequest) ? _domainConnectRequestVersion : 0; + auto domainPacket = NLPacket::create(domainPacketType, -1, false, false, packetVersion); QDataStream packetStream(domainPacket.get()); @@ -312,12 +313,20 @@ void NodeList::sendDomainServerCheckIn() { // pack the connect UUID for this connect request packetStream << connectUUID; + + // include the protocol version signature in our connect request + if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { + QByteArray protocolVersionSig = protocolVersionsSignature(); + packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size()); + } } // pack our data to send to the domain-server including // the hostname information (so the domain-server can see which place name we came in on) - packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList() - << DependencyManager::get()->getPlaceName(); + packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); + if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasHostname)) { + packetStream << DependencyManager::get()->getPlaceName(); + } if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 4b196d5f7b..b269554e77 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -68,6 +68,9 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } + /// downgrades the DomainConnnectRequest PacketVersion to attempt to probe for older domain servers + void downgradeDomainServerCheckInVersion() { _domainConnectRequestVersion--; } + public slots: void reset(); void sendDomainServerCheckIn(); @@ -85,6 +88,9 @@ public slots: void processICEPingPacket(QSharedPointer message); + void resetDomainServerCheckInVersion() + { _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); } + signals: void limitOfSilentDomainCheckInsReached(); void receivedDomainServerList(); @@ -123,6 +129,8 @@ private: HifiSockAddr _assignmentServerSocket; bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; + + PacketVersion _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); }; #endif // hifi_NodeList_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 915c2f44ba..92e5b52f75 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -12,7 +12,9 @@ #include "PacketHeaders.h" #include +#include +#include #include #include @@ -47,10 +49,12 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_NO_FLY_ZONES; + return VERSION_ENTITIES_MORE_SHAPES; + case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: - return static_cast(AvatarMixerPacketVersion::AvatarEntities); + case PacketType::KillAvatar: + return static_cast(AvatarMixerPacketVersion::AbsoluteSixByteRotations); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: @@ -58,9 +62,13 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetUpload: // Removal of extension from Asset requests return 18; + + case PacketType::DomainConnectionDenied: + return static_cast(DomainConnectionDeniedVersion::IncludesReasonCode); + case PacketType::DomainConnectRequest: - // addition of referring hostname information - return 18; + return static_cast(DomainConnectRequestVersion::HasProtocolVersions); + default: return 17; } @@ -80,3 +88,36 @@ QDebug operator<<(QDebug debug, const PacketType& type) { debug.nospace().noquote() << (uint8_t) type << " (" << typeName << ")"; return debug.space(); } + +#if (PR_BUILD || DEV_BUILD) +static bool sendWrongProtocolVersion = false; +void sendWrongProtocolVersionsSignature(bool sendWrongVersion) { + sendWrongProtocolVersion = sendWrongVersion; +} +#endif + +QByteArray protocolVersionsSignature() { + static QByteArray protocolVersionSignature; + static std::once_flag once; + std::call_once(once, [&] { + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + uint8_t numberOfProtocols = static_cast(PacketType::LAST_PACKET_TYPE) + 1; + stream << numberOfProtocols; + for (uint8_t packetType = 0; packetType < numberOfProtocols; packetType++) { + uint8_t packetTypeVersion = static_cast(versionForPacketType(static_cast(packetType))); + stream << packetTypeVersion; + } + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(buffer); + protocolVersionSignature = hash.result(); + }); + + #if (PR_BUILD || DEV_BUILD) + if (sendWrongProtocolVersion) { + return QByteArray("INCORRECTVERSION"); // only for debugging version checking + } + #endif + + return protocolVersionSignature; +} diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 030b4af8c9..97398c6744 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -61,7 +61,7 @@ public: AssignmentClientStatus, NoisyMute, AvatarIdentity, - AvatarBillboard, + TYPE_UNUSED_1, DomainConnectRequest, DomainServerRequireDTLS, NodeJsonStats, @@ -94,7 +94,8 @@ public: ICEServerHeartbeatDenied, AssetMappingOperation, AssetMappingOperationReply, - ICEServerHeartbeatACK + ICEServerHeartbeatACK, + LAST_PACKET_TYPE = ICEServerHeartbeatACK }; }; @@ -109,6 +110,11 @@ extern const QSet NON_SOURCED_PACKETS; extern const QSet RELIABLE_PACKETS; PacketVersion versionForPacketType(PacketType packetType); +QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols + +#if (PR_BUILD || DEV_BUILD) +void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation +#endif uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); @@ -172,11 +178,24 @@ const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55; const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56; const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; +const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, SoftAttachmentSupport, - AvatarEntities + AvatarEntities, + AbsoluteSixByteRotations +}; + +enum class DomainConnectRequestVersion : PacketVersion { + NoHostname = 17, + HasHostname, + HasProtocolVersions +}; + +enum class DomainConnectionDeniedVersion : PacketVersion { + ReasonMessageOnly = 17, + IncludesReasonCode }; #endif // hifi_PacketHeaders_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9ef16b2daa..8011b0c4c8 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -726,10 +726,6 @@ glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { return translatedPoint; } -bool Model::getJointState(int index, glm::quat& rotation) const { - return _rig->getJointStateRotation(index, rotation); -} - void Model::clearJointState(int index) { _rig->clearJointState(index); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 581184918d..6a7c9ec560 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -252,10 +252,6 @@ protected: /// Returns the scaled equivalent of a point in model space. glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const; - /// Fetches the joint state at the specified index. - /// \return whether or not the joint state is "valid" (that is, non-default) - bool getJointState(int index, glm::quat& rotation) const; - /// Clear the joint states void clearJointState(int index); diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index fc72f094e7..8c9ff2c8ad 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -66,7 +66,7 @@ void main(void) { vec3 fragEyeDir = normalize(fragEyeVector.xyz); vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); - // Eval attenuation + // Eval attenuation float radialAttenuation = evalLightAttenuation(light, fragLightDistance); // Final Lighting color diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 0d16b26fdf..4de12af62c 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -19,7 +19,7 @@ class ScriptAudioInjector : public QObject { Q_OBJECT - Q_PROPERTY(bool isPlaying READ isPlaying) + Q_PROPERTY(bool playing READ isPlaying) Q_PROPERTY(float loudness READ getLoudness) Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c9d5ca35b0..a5e3be8a43 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -941,7 +941,13 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { } -void ScriptEngine::stop() { +void ScriptEngine::stop(bool marshal) { + _isStopping = true; // this can be done on any thread + + if (marshal) { + QMetaObject::invokeMethod(this, "stop"); + return; + } if (!_isFinished) { _isFinished = true; emit runningStateChanged(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 80978e4527..1077dce686 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -84,7 +84,7 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(); + Q_INVOKABLE void stop(bool marshal = false); // Stop any evaluating scripts and wait for the scripting thread to finish. void waitTillDoneRunning(); @@ -147,6 +147,9 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget + // this is used by code in ScriptEngines.cpp during the "reload all" operation + bool isStopping() const { return _isStopping; } + bool isDebuggable() const { return _debuggable; } void disconnectNonEssentialSignals(); @@ -189,6 +192,7 @@ protected: QString _parentURL; std::atomic _isFinished { false }; std::atomic _isRunning { false }; + std::atomic _isStopping { false }; int _evaluatesPending { 0 }; bool _isInitialized { false }; QHash _timerFunctionMap; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 4fd680025a..29c223f4b3 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -380,7 +380,7 @@ void ScriptEngines::stopAllScripts(bool restart) { // Stop and possibly restart all currently running scripts for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); it != _scriptEnginesHash.constEnd(); it++) { - if (it.value()->isFinished()) { + if (it.value()->isFinished() || it.value()->isStopping()) { continue; } if (restart && it.value()->isUserLoaded()) { @@ -388,8 +388,7 @@ void ScriptEngines::stopAllScripts(bool restart) { reloadScript(scriptName); }); } - QMetaObject::invokeMethod(it.value(), "stop"); - //it.value()->stop(); + it.value()->stop(true); qCDebug(scriptengine) << "stopping script..." << it.key(); } } @@ -460,7 +459,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL } auto scriptEngine = getScriptEngine(scriptUrl); - if (scriptEngine) { + if (scriptEngine && !scriptEngine->isStopping()) { return scriptEngine; } diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 784e9ce5af..556c313f95 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -135,6 +135,87 @@ int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatO return sizeof(quatParts); } +#define HI_BYTE(x) (uint8_t)(x >> 8) +#define LO_BYTE(x) (uint8_t)(0xff & x) + +int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput) { + + // find largest component + uint8_t largestComponent = 0; + for (int i = 1; i < 4; i++) { + if (fabs(quatInput[i]) > fabs(quatInput[largestComponent])) { + largestComponent = i; + } + } + + // ensure that the sign of the dropped component is always negative. + glm::quat q = quatInput[largestComponent] > 0 ? -quatInput : quatInput; + + const float MAGNITUDE = 1.0f / sqrtf(2.0f); + const uint32_t NUM_BITS_PER_COMPONENT = 15; + const uint32_t RANGE = (1 << NUM_BITS_PER_COMPONENT) - 1; + + // quantize the smallest three components into integers + uint16_t components[3]; + for (int i = 0, j = 0; i < 4; i++) { + if (i != largestComponent) { + // transform component into 0..1 range. + float value = (q[i] + MAGNITUDE) / (2.0f * MAGNITUDE); + + // quantize 0..1 into 0..range + components[j] = (uint16_t)(value * RANGE); + j++; + } + } + + // encode the largestComponent into the high bits of the first two components + components[0] = (0x7fff & components[0]) | ((0x01 & largestComponent) << 15); + components[1] = (0x7fff & components[1]) | ((0x02 & largestComponent) << 14); + + buffer[0] = HI_BYTE(components[0]); + buffer[1] = LO_BYTE(components[0]); + buffer[2] = HI_BYTE(components[1]); + buffer[3] = LO_BYTE(components[1]); + buffer[4] = HI_BYTE(components[2]); + buffer[5] = LO_BYTE(components[2]); + + return 6; +} + +int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput) { + + uint16_t components[3]; + components[0] = ((uint16_t)(0x7f & buffer[0]) << 8) | buffer[1]; + components[1] = ((uint16_t)(0x7f & buffer[2]) << 8) | buffer[3]; + components[2] = ((uint16_t)(0x7f & buffer[4]) << 8) | buffer[5]; + + // largestComponent is encoded into the highest bits of the first 2 components + uint8_t largestComponent = ((0x80 & buffer[2]) >> 6) | ((0x80 & buffer[0]) >> 7); + + const uint32_t NUM_BITS_PER_COMPONENT = 15; + const float RANGE = (float)((1 << NUM_BITS_PER_COMPONENT) - 1); + const float MAGNITUDE = 1.0f / sqrtf(2.0f); + float floatComponents[3]; + for (int i = 0; i < 3; i++) { + floatComponents[i] = ((float)components[i] / RANGE) * (2.0f * MAGNITUDE) - MAGNITUDE; + } + + // missingComponent is always negative. + float missingComponent = -sqrtf(1.0f - floatComponents[0] * floatComponents[0] - floatComponents[1] * floatComponents[1] - floatComponents[2] * floatComponents[2]); + + for (int i = 0, j = 0; i < 4; i++) { + if (i != largestComponent) { + quatOutput[i] = floatComponents[j]; + j++; + } else { + quatOutput[i] = missingComponent; + } + } + + return 6; +} + + // Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's // http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 8b1446d4e5..ae9ec25195 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -97,6 +97,14 @@ int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destina int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput); int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput); +// alternate compression method that picks the smallest three quaternion components. +// and packs them into 15 bits each. An additional 2 bits are used to encode which component +// was omitted. Also because the components are encoded from the -1/sqrt(2) to 1/sqrt(2) which +// gives us some extra precision over the -1 to 1 range. The final result will have a maximum +// error of +- 4.3e-5 error per compoenent. +int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput); +int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput); + // Ratios need the be highly accurate when less than 10, but not very accurate above 10, and they // are never greater than 1000 to 1, this allows us to encode each component in 16bits int packFloatRatioToTwoByte(unsigned char* buffer, float ratio); diff --git a/libraries/shared/src/Packed.h b/libraries/shared/src/Packed.h new file mode 100644 index 0000000000..3300634b96 --- /dev/null +++ b/libraries/shared/src/Packed.h @@ -0,0 +1,12 @@ +#ifndef hifi_Packed_h +#define hifi_Packed_h + +#if defined(_MSC_VER) +#define PACKED_BEGIN __pragma(pack(push, 1)) +#define PACKED_END __pragma(pack(pop)); +#else +#define PACKED_BEGIN +#define PACKED_END __attribute__((__packed__)); +#endif + +#endif diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 12567b10d1..6e75454b5f 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -282,7 +282,22 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u for (uint32_t i = 0; i < vr::k_unControllerStateAxisCount; i++) { handleAxisEvent(deltaTime, i, controllerState.rAxis[i].x, controllerState.rAxis[i].y, isLeftHand); } - } + + // pseudo buttons the depend on both of the above for-loops + partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_OUTER); + partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_OUTER); + } + } +} + +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int outerPseudoButton) { + // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. + const float CENTER_DEADBAND = 0.6f; + if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { + float absX = abs(_axisStateMap[xAxis]); + float absY = abs(_axisStateMap[yAxis]); + bool isCenter = (absX < CENTER_DEADBAND) && (absY < CENTER_DEADBAND); // square deadband + _buttonPressedMap.insert(isCenter ? centerPseudoButton : outerPseudoButton); } } @@ -443,6 +458,11 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI // touch pad press makePair(LS, "LS"), makePair(RS, "RS"), + // Differentiate where we are in the touch pad click + makePair(LS_CENTER, "LSCenter"), + makePair(LS_OUTER, "LSOuter"), + makePair(RS_CENTER, "RSCenter"), + makePair(RS_OUTER, "RSOuter"), // triggers makePair(LT, "LT"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 672ad59cfe..bd5d4a39f4 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,6 +61,7 @@ private: void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); + void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int outerPseudoButton); class FilteredStick { public: diff --git a/script-archive/FlockOfbirds.js b/script-archive/FlockOfbirds.js index f466fa2909..c2fb54f0a6 100644 --- a/script-archive/FlockOfbirds.js +++ b/script-archive/FlockOfbirds.js @@ -3,8 +3,8 @@ // examples // // Copyright 2014 High Fidelity, Inc. -// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined -// at the start of the script. +// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined +// at the start of the script. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,12 +13,12 @@ // The rectangular area in the domain where the flock will fly var lowerCorner = { x: 0, y: 0, z: 0 }; var upperCorner = { x: 30, y: 10, z: 30 }; -var STARTING_FRACTION = 0.25; +var STARTING_FRACTION = 0.25; var NUM_BIRDS = 50; var UPDATE_INTERVAL = 0.016; -var playSounds = true; -var SOUND_PROBABILITY = 0.001; +var playSounds = true; +var SOUND_PROBABILITY = 0.001; var STARTING_LIFETIME = (1.0 / SOUND_PROBABILITY) * UPDATE_INTERVAL * 10; var numPlaying = 0; var BIRD_SIZE = 0.08; @@ -36,17 +36,17 @@ var ALIGNMENT_FORCE = 1.5; var COHESION_FORCE = 1.0; var MAX_COHESION_VELOCITY = 0.5; -var followBirds = false; +var followBirds = false; var AVATAR_FOLLOW_RATE = 0.001; var AVATAR_FOLLOW_VELOCITY_TIMESCALE = 2.0; var AVATAR_FOLLOW_ORIENTATION_RATE = 0.005; -var floor = false; +var floor = false; var MAKE_FLOOR = false; var averageVelocity = { x: 0, y: 0, z: 0 }; var averagePosition = { x: 0, y: 0, z: 0 }; -var birdsLoaded = false; +var birdsLoaded = false; var oldAvatarOrientation; var oldAvatarPosition; @@ -79,10 +79,10 @@ function updateBirds(deltaTime) { birds[i].entityId = false; return; } - // Sum up average position and velocity + // Sum up average position and velocity if (Vec3.length(properties.velocity) > MIN_ALIGNMENT_VELOCITY) { sumVelocity = Vec3.sum(sumVelocity, properties.velocity); - birdVelocitiesCounted += 1; + birdVelocitiesCounted += 1; } sumPosition = Vec3.sum(sumPosition, properties.position); birdPositionsCounted += 1; @@ -93,10 +93,10 @@ function updateBirds(deltaTime) { var randomVelocity = randomVector(RANDOM_FLAP_VELOCITY); randomVelocity.y = FLAP_UP + Math.random() * FLAP_UP; - // Alignment Velocity - var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); + // Alignment Velocity + var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); var alignmentVelocity = Vec3.multiply(alignmentVelocityMagnitude, Vec3.normalize(averageVelocity)); - alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; + alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; // Cohesion var distanceFromCenter = Vec3.length(Vec3.subtract(averagePosition, properties.position)); @@ -107,10 +107,10 @@ function updateBirds(deltaTime) { Entities.editEntity(birds[i].entityId, { velocity: Vec3.sum(properties.velocity, newVelocity) }); - } + } // Check whether to play a chirp - if (playSounds && (!birds[i].audioId || !birds[i].audioId.isPlaying) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { + if (playSounds && (!birds[i].audioId || !birds[i].audioId.playing) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { var options = { position: properties.position, volume: BIRD_MASTER_VOLUME @@ -126,43 +126,43 @@ function updateBirds(deltaTime) { // Change size, and update lifetime to keep bird alive Entities.editEntity(birds[i].entityId, { dimensions: Vec3.multiply(1.5, properties.dimensions), lifetime: properties.ageInSeconds + STARTING_LIFETIME}); - + } else if (birds[i].audioId) { - // If bird is playing a chirp - if (!birds[i].audioId.isPlaying) { + // If bird is playing a chirp + if (!birds[i].audioId.playing) { Entities.editEntity(birds[i].entityId, { dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }}); numPlaying--; - } + } } // Keep birds in their 'cage' var bounce = false; - var newVelocity = properties.velocity; - var newPosition = properties.position; + var newVelocity = properties.velocity; + var newPosition = properties.position; if (properties.position.x < lowerCorner.x) { - newPosition.x = lowerCorner.x; + newPosition.x = lowerCorner.x; newVelocity.x *= -1.0; bounce = true; } else if (properties.position.x > upperCorner.x) { - newPosition.x = upperCorner.x; + newPosition.x = upperCorner.x; newVelocity.x *= -1.0; bounce = true; } if (properties.position.y < lowerCorner.y) { - newPosition.y = lowerCorner.y; + newPosition.y = lowerCorner.y; newVelocity.y *= -1.0; bounce = true; } else if (properties.position.y > upperCorner.y) { - newPosition.y = upperCorner.y; + newPosition.y = upperCorner.y; newVelocity.y *= -1.0; bounce = true; - } + } if (properties.position.z < lowerCorner.z) { - newPosition.z = lowerCorner.z; + newPosition.z = lowerCorner.z; newVelocity.z *= -1.0; bounce = true; } else if (properties.position.z > upperCorner.z) { - newPosition.z = upperCorner.z; + newPosition.z = upperCorner.z; newVelocity.z *= -1.0; bounce = true; } @@ -171,7 +171,7 @@ function updateBirds(deltaTime) { } } } - // Update average velocity and position of flock + // Update average velocity and position of flock if (birdVelocitiesCounted > 0) { averageVelocity = Vec3.multiply(1.0 / birdVelocitiesCounted, sumVelocity); //print(Vec3.length(averageVelocity)); @@ -184,10 +184,10 @@ function updateBirds(deltaTime) { MyAvatar.orientation = Quat.mix(MyAvatar.orientation, birdDirection, AVATAR_FOLLOW_ORIENTATION_RATE); } } - } + } if (birdPositionsCounted > 0) { averagePosition = Vec3.multiply(1.0 / birdPositionsCounted, sumPosition); - // If Following birds, update position + // If Following birds, update position if (followBirds) { MyAvatar.position = Vec3.sum(Vec3.multiply(AVATAR_FOLLOW_RATE, MyAvatar.position), Vec3.multiply(1.0 - AVATAR_FOLLOW_RATE, averagePosition)); } @@ -211,12 +211,12 @@ Script.scriptEnding.connect(function() { }); function loadBirds(howMany) { - oldAvatarOrientation = MyAvatar.orientation; + oldAvatarOrientation = MyAvatar.orientation; oldAvatarPosition = MyAvatar.position; var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw"]; /* Here are more sounds/species you can use - , "mexicanWhipoorwill.raw", + , "mexicanWhipoorwill.raw", "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", @@ -252,19 +252,19 @@ function loadBirds(howMany) { { red: 216, green: 153, blue: 99 }, { red: 242, green: 226, blue: 64 } ]; - + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; - + for (var i = 0; i < howMany; i++) { var whichBird = Math.floor(Math.random() * sound_filenames.length); - var position = { - x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, - y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, + var position = { + x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, + y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, z: lowerCorner.z + (upperCorner.z - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION - }; + }; birds.push({ - sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), entityId: Entities.addEntity({ type: "Sphere", position: position, @@ -282,8 +282,8 @@ function loadBirds(howMany) { } if (MAKE_FLOOR) { var FLOOR_THICKNESS = 0.05; - floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, - y: lowerCorner.y, + floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, + y: lowerCorner.y, z: lowerCorner.z + (upperCorner.z - lowerCorner.z) / 2.0 }, dimensions: { x: (upperCorner.x - lowerCorner.x), y: FLOOR_THICKNESS, z: (upperCorner.z - lowerCorner.z)}, color: {red: 100, green: 100, blue: 100} diff --git a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js index 381a7ee902..30567b4fc7 100644 --- a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js +++ b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js @@ -49,7 +49,7 @@ function debug() { // Display the arguments not just [Object object]. EntityViewer.setCenterRadius(QUERY_RADIUS); // ENTITY DATA CACHE -// +// var entityCache = {}; // A dictionary of unexpired EntityData objects. var examinationCount = 0; function EntityDatum(entityIdentifier) { // Just the data of an entity that we need to know about. @@ -146,7 +146,7 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n return; } that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC - if (!that.injector.isPlaying) { // Subtle: a looping sound will not check playbackGap. + if (!that.injector.playing) { // Subtle: a looping sound will not check playbackGap. if (repeat()) { // WAITING => PLAYING // Setup next play just once, now. Changes won't be looked at while we wait. that.playAfter = randomizedNextPlay(); @@ -208,7 +208,7 @@ function updateAllEntityData() { // A fast update of all entities we know about. stats.entities++; if (datum.url) { stats.sounds++; - if (datum.injector && datum.injector.isPlaying) { + if (datum.injector && datum.injector.playing) { stats.playing++; } } diff --git a/script-archive/avatarSelector.js b/script-archive/avatarSelector.js index dc2916a1a8..47740ef0b3 100644 --- a/script-archive/avatarSelector.js +++ b/script-archive/avatarSelector.js @@ -283,7 +283,7 @@ function actionStartEvent(event) { if (avatarIndex < avatars.length) { var actionPlace = avatars[avatarIndex]; - print("Changing avatar to " + actionPlace.name + print("Changing avatar to " + actionPlace.name + " after click on panel " + panelIndex + " with avatar index " + avatarIndex); MyAvatar.useFullAvatarURL(actionPlace.content_url); @@ -395,7 +395,7 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + if (currentMuzakInjector && !currentMuzakInjector.playing) { playNextMuzak(); } } diff --git a/script-archive/baseball/baseballCrowd.js b/script-archive/baseball/baseballCrowd.js index de9b53f9ec..1459ce6e67 100644 --- a/script-archive/baseball/baseballCrowd.js +++ b/script-archive/baseball/baseballCrowd.js @@ -21,7 +21,7 @@ var CHATTER_VOLUME = 0.20 var EXTRA_VOLUME = 0.25 function playChatter() { - if (chatter.downloaded && !chatter.isPlaying) { + if (chatter.downloaded && !chatter.playing) { Audio.playSound(chatter, { loop: true, volume: CHATTER_VOLUME }); } } @@ -31,7 +31,7 @@ chatter.ready.connect(playChatter); var currentInjector = null; function playRandomExtras() { - if ((!currentInjector || !currentInjector.isPlaying) && (Math.random() < (1.0 / 1800.0))) { + if ((!currentInjector || !currentInjector.playing) && (Math.random() < (1.0 / 1800.0))) { // play a random extra sound about every 30s currentInjector = Audio.playSound( extras[Math.floor(Math.random() * extras.length)], diff --git a/script-archive/controllers/hydra/airGuitar.js b/script-archive/controllers/hydra/airGuitar.js index f8606808c1..73c7099eed 100644 --- a/script-archive/controllers/hydra/airGuitar.js +++ b/script-archive/controllers/hydra/airGuitar.js @@ -22,12 +22,12 @@ function printVector(v) { return; } -function vMinus(a, b) { +function vMinus(a, b) { var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; return rval; } -// The model file to be used for the guitar +// The model file to be used for the guitar var guitarModel = HIFI_PUBLIC_BUCKET + "models/attachments/guitar.fst"; // Load sounds that will be played @@ -47,7 +47,7 @@ chords[6] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Me chords[7] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Metal+E+short.raw"); chords[8] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Metal+G+short.raw"); -// Steel Guitar +// Steel Guitar chords[9] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+A.raw"); chords[10] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+B.raw"); chords[11] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+E.raw"); @@ -83,8 +83,8 @@ if (leftHanded) { } var lastPosition = { x: 0.0, - y: 0.0, - z: 0.0 }; + y: 0.0, + z: 0.0 }; var audioInjector = null; var selectorPressed = false; @@ -106,7 +106,7 @@ function checkHands(deltaTime) { var chord = Controller.getValue(chordTrigger); if (volume > 1.0) volume = 1.0; - if ((chord > 0.1) && audioInjector && audioInjector.isPlaying) { + if ((chord > 0.1) && audioInjector && audioInjector.playing) { // If chord finger trigger pulled, stop current chord print("stopping chord because cord trigger pulled"); audioInjector.stop(); @@ -119,7 +119,7 @@ function checkHands(deltaTime) { guitarSelector += NUM_CHORDS; if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { guitarSelector = 0; - } + } print("new guitarBase: " + guitarSelector); stopAudio(true); selectorPressed = true; @@ -160,7 +160,7 @@ function checkHands(deltaTime) { } function stopAudio(killInjector) { - if (audioInjector && audioInjector.isPlaying) { + if (audioInjector && audioInjector.playing) { print("stopped sound"); audioInjector.stop(); } @@ -212,4 +212,3 @@ function scriptEnding() { Script.update.connect(checkHands); Script.scriptEnding.connect(scriptEnding); Controller.keyPressEvent.connect(keyPressEvent); - diff --git a/script-archive/drylake/ratCreator.js b/script-archive/drylake/ratCreator.js index 60ccf1a1a3..6f6b322f84 100644 --- a/script-archive/drylake/ratCreator.js +++ b/script-archive/drylake/ratCreator.js @@ -340,7 +340,7 @@ function moveRats() { var metaRat = getMetaRatByRat(rat); if (metaRat !== undefined) { if (metaRat.injector !== undefined) { - if (metaRat.injector.isPlaying === true) { + if (metaRat.injector.playing === true) { metaRat.injector.options = { loop: true, position: ratPosition diff --git a/script-archive/entityScripts/movable.js b/script-archive/entityScripts/movable.js index b7ecfbbc8e..06b30ce15e 100644 --- a/script-archive/entityScripts/movable.js +++ b/script-archive/entityScripts/movable.js @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function(){ +(function(){ this.entityID = null; this.properties = null; @@ -30,13 +30,13 @@ "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove2.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove3.wav" ]; - + this.turnSoundURLS = [ "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove1.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove2.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove3.wav" - + // TODO: determine if these or other turn sounds work better than move sounds. //"http://public.highfidelity.io/sounds/MovingFurniture/FurnitureTurn1.wav", //"http://public.highfidelity.io/sounds/MovingFurniture/FurnitureTurn2.wav", @@ -50,7 +50,7 @@ this.turnSound = null; this.moveInjector = null; this.turnInjector = null; - + var debug = false; var displayRotateTargets = true; // change to false if you don't want the rotate targets var rotateOverlayTargetSize = 10000; // really big target @@ -61,12 +61,12 @@ var yawZero; var rotationNormal; var yawNormal; - var stopSoundDelay = 100; // number of msecs of not moving to have sound stop - + var stopSoundDelay = 100; // number of msecs of not moving to have sound stop + this.getRandomInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; - } - + } + this.downloadSounds = function() { for (var i = 0; i < this.moveSoundURLS.length; i++) { this.moveSounds[i] = SoundCache.getSound(this.moveSoundURLS[i]); @@ -95,7 +95,7 @@ if (debug) { print("playMoveSound() --- calling this.moveInjector = Audio.playSound(this.moveSound...)"); } - + if (!this.moveInjector) { this.moveInjector = Audio.playSound(this.moveSound, { position: this.properties.position, loop: true, volume: 0.1 }); } else { @@ -148,7 +148,7 @@ var upVector = { x: 0, y: 1, z: 0 }; var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, this.properties.position, upVector); - + var newPosition = Vec3.sum(intersection, this.graboffset); Entities.editEntity(this.entityID, { position: newPosition }); }; @@ -158,7 +158,7 @@ var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var upVector = { x: 0, y: 1, z: 0 }; var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, - this.properties.position, upVector); + this.properties.position, upVector); this.graboffset = Vec3.subtract(this.properties.position, intersection); }; @@ -183,18 +183,18 @@ this.lastMovedPosition.y = mouseEvent.y; } } - + this.move = function(mouseEvent) { this.updatePosition(mouseEvent); - if (this.moveInjector === null || !this.moveInjector.isPlaying) { + if (this.moveInjector === null || !this.moveInjector.playing) { this.playMoveSound(); } }; - + this.release = function(mouseEvent) { this.updatePosition(mouseEvent); }; - + this.rotate = function(mouseEvent) { var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var result = Overlays.findRayIntersection(pickRay); @@ -205,7 +205,7 @@ var centerToZero = Vec3.subtract(center, zero); var centerToIntersect = Vec3.subtract(center, result.intersection); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - + var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = false; // var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -213,10 +213,10 @@ angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; snapToInner = true; } - + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); Entities.editEntity(this.entityID, { rotation: Quat.multiply(yawChange, this.originalRotation) }); - + // update the rotation display accordingly... var startAtCurrent = 360-angleFromZero; @@ -245,7 +245,7 @@ } } - if (this.turnInjector === null || !this.turnInjector.isPlaying) { + if (this.turnInjector === null || !this.turnInjector.playing) { this.playTurnSound(); } }; @@ -267,7 +267,7 @@ this.rotateOverlayOuter = null; this.rotateOverlayCurrent = null; } - + this.displayRotateOverlay = function(mouseEvent) { var yawOverlayAngles = { x: 90, y: 0, z: 0 }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); @@ -356,14 +356,14 @@ var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var result = Overlays.findRayIntersection(pickRay); yawZero = result.intersection; - + }; - + this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties this.downloadSounds(); }; - + this.clickDownOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties this.grab(mouseEvent); @@ -372,13 +372,13 @@ var nowMSecs = nowDate.getTime(); this.clickedAt = nowMSecs; this.firstHolding = true; - + this.clicked.x = mouseEvent.x; this.clicked.y = mouseEvent.y; this.lastMovedPosition.x = mouseEvent.x; this.lastMovedPosition.y = mouseEvent.y; this.lastMovedMSecs = nowMSecs; - + this.pickRandomSounds(); }; @@ -391,7 +391,7 @@ if (this.clicked.x == mouseEvent.x && this.clicked.y == mouseEvent.y) { var d = new Date(); var now = d.getTime(); - + if (now - this.clickedAt > 500) { this.displayRotateOverlay(mouseEvent); this.firstHolding = false; @@ -402,13 +402,13 @@ this.firstHolding = false; } } - + if (this.rotateMode) { this.rotate(mouseEvent); } else { this.move(mouseEvent); } - + this.stopSoundIfNotMoving(mouseEvent); }; this.clickReleaseOnEntity = function(entityID, mouseEvent) { @@ -418,7 +418,7 @@ } else { this.release(mouseEvent); } - + if (this.rotateOverlayTarget != null) { this.cleanupRotateOverlay(); this.rotateMode = false; diff --git a/script-archive/example/audio/birdSongs.js b/script-archive/example/audio/birdSongs.js index 557fc81f5b..9e949a19ed 100644 --- a/script-archive/example/audio/birdSongs.js +++ b/script-archive/example/audio/birdSongs.js @@ -22,7 +22,7 @@ var BIRD_VELOCITY = 2.0; var LIGHT_RADIUS = 10.0; var BIRD_MASTER_VOLUME = 0.5; -var useLights = true; +var useLights = true; function randomVector(scale) { return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; @@ -33,11 +33,11 @@ function maybePlaySound(deltaTime) { // Set the location and other info for the sound to play var whichBird = Math.floor(Math.random() * birds.length); //print("playing sound # " + whichBird); - var position = { - x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), - y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), - z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) - }; + var position = { + x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), + y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), + z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) + }; var options = { position: position, volume: BIRD_MASTER_VOLUME @@ -63,31 +63,31 @@ function maybePlaySound(deltaTime) { constantAttenuation: 0, linearAttenuation: 4.0, - quadraticAttenuation: 2.0, + quadraticAttenuation: 2.0, lifetime: 10 }); } - + playing.push({ audioId: Audio.playSound(birds[whichBird].sound, options), entityId: entityId, lightId: lightId, color: birds[whichBird].color }); } if (playing.length != numPlaying) { numPlaying = playing.length; //print("number playing = " + numPlaying); - } + } for (var i = 0; i < playing.length; i++) { - if (!playing[i].audioId.isPlaying) { + if (!playing[i].audioId.playing) { Entities.deleteEntity(playing[i].entityId); if (useLights) { Entities.deleteEntity(playing[i].lightId); - } + } playing.splice(i, 1); } else { var loudness = playing[i].audioId.loudness; var newColor = { red: playing[i].color.red, green: playing[i].color.green, blue: playing[i].color.blue }; if (loudness > 0.05) { - newColor.red *= (1.0 - loudness); - newColor.green *= (1.0 - loudness); - newColor.blue *= (1.0 - loudness); + newColor.red *= (1.0 - loudness); + newColor.green *= (1.0 - loudness); + newColor.blue *= (1.0 - loudness); } var properties = Entities.getEntityProperties(playing[i].entityId); var newPosition = Vec3.sum(properties.position, randomVector(BIRD_VELOCITY * deltaTime)); @@ -120,7 +120,7 @@ Script.scriptEnding.connect(function() { }); function loadBirds() { - var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", + var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", @@ -155,13 +155,13 @@ function loadBirds() { { red: 216, green: 153, blue: 99 }, { red: 242, green: 226, blue: 64 } ]; - + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; - + for (var i = 0; i < sound_filenames.length; i++) { birds.push({ - sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[i]), - color: colors[i] + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[i]), + color: colors[i] }); } -} \ No newline at end of file +} diff --git a/script-archive/lobby.js b/script-archive/lobby.js index 3095740c93..6fa4a42cb6 100644 --- a/script-archive/lobby.js +++ b/script-archive/lobby.js @@ -88,19 +88,19 @@ var DRONE_VOLUME = 0.3; function drawLobby() { if (!panelWall) { print("Adding overlays for the lobby panel wall and orb shell."); - + var cameraEuler = Quat.safeEulerAngles(Camera.orientation); var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0}); - + var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT)); - + var panelWallProps = { url: LOBBY_PANEL_WALL_URL, position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)), rotation: towardsMe, dimensions: panelsDimensions }; - + var orbShellProps = { url: LOBBY_SHELL_URL, position: orbPosition, @@ -128,13 +128,13 @@ function drawLobby() { visible: false, isFacingAvatar: true }; - + avatarStickPosition = MyAvatar.position; - panelWall = Overlays.addOverlay("model", panelWallProps); + panelWall = Overlays.addOverlay("model", panelWallProps); orbShell = Overlays.addOverlay("model", orbShellProps); descriptionText = Overlays.addOverlay("text3d", descriptionTextProps); - + if (droneSound.downloaded) { // start the drone sound if (!currentDrone) { @@ -143,7 +143,7 @@ function drawLobby() { currentDrone.restart(); } } - + // start one of our muzak sounds playRandomMuzak(); } @@ -157,31 +157,31 @@ function changeLobbyTextures() { req.send(); places = JSON.parse(req.responseText).data.places; - + var NUM_PANELS = places.length; - var textureProp = { + var textureProp = { textures: {} }; - + for (var j = 0; j < NUM_PANELS; j++) { var panelIndex = placeIndexToPanelIndex(j); textureProp["textures"]["file" + panelIndex] = places[j].previews.lobby; }; - + Overlays.editOverlay(panelWall, textureProp); } var MUZAK_VOLUME = 0.1; -function playCurrentSound(secondOffset) { +function playCurrentSound(secondOffset) { if (currentSound == latinSound) { if (!latinInjector) { latinInjector = Audio.playSound(latinSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME }); } else { latinInjector.restart(); } - + currentMuzakInjector = latinInjector; } else if (currentSound == elevatorSound) { if (!elevatorInjector) { @@ -189,7 +189,7 @@ function playCurrentSound(secondOffset) { } else { elevatorInjector.restart(); } - + currentMuzakInjector = elevatorInjector; } } @@ -205,14 +205,14 @@ function playNextMuzak() { currentSound = latinSound; } } - + playCurrentSound(0); } } function playRandomMuzak() { currentSound = null; - + if (latinSound.downloaded && elevatorSound.downloaded) { currentSound = Math.random() < 0.5 ? latinSound : elevatorSound; } else if (latinSound.downloaded) { @@ -220,11 +220,11 @@ function playRandomMuzak() { } else if (elevatorSound.downloaded) { currentSound = elevatorSound; } - + if (currentSound) { // pick a random number of seconds from 0-10 to offset the muzak var secondOffset = Math.random() * 10; - + playCurrentSound(secondOffset); } else { currentMuzakInjector = null; @@ -233,36 +233,36 @@ function playRandomMuzak() { function cleanupLobby() { toggleEnvironmentRendering(true); - + // for each of the 21 placeholder textures, set them back to default so the cached model doesn't have changed textures var panelTexturesReset = {}; panelTexturesReset["textures"] = {}; - - for (var j = 0; j < MAX_NUM_PANELS; j++) { + + for (var j = 0; j < MAX_NUM_PANELS; j++) { panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL; }; - + Overlays.editOverlay(panelWall, panelTexturesReset); - + Overlays.deleteOverlay(panelWall); Overlays.deleteOverlay(orbShell); Overlays.deleteOverlay(descriptionText); - + panelWall = false; orbShell = false; - + if (currentDrone) { currentDrone.stop(); currentDrone = null } - + if (currentMuzakInjector) { currentMuzakInjector.stop(); currentMuzakInjector = null; } - + places = {}; - + } function actionStartEvent(event) { @@ -271,19 +271,19 @@ function actionStartEvent(event) { // check if we hit a panel and if we should jump there var result = Overlays.findRayIntersection(event.actionRay); if (result.intersects && result.overlayID == panelWall) { - + var panelName = result.extraInfo; - + var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); var placeIndex = panelIndexToPlaceIndex(panelIndex); if (placeIndex < places.length) { var actionPlace = places[placeIndex]; - - print("Jumping to " + actionPlace.name + " at " + actionPlace.address + + print("Jumping to " + actionPlace.name + " at " + actionPlace.address + " after click on panel " + panelIndex + " with place index " + placeIndex); - + Window.location = actionPlace.address; maybeCleanupLobby(); } @@ -328,7 +328,7 @@ function handleLookAt(pickRay) { var placeIndex = panelIndexToPlaceIndex(panelIndex); if (placeIndex < places.length) { var actionPlace = places[placeIndex]; - + if (actionPlace.description == "") { Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText }); } else { @@ -378,7 +378,7 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + if (currentMuzakInjector && !currentMuzakInjector.playing) { playNextMuzak(); } } diff --git a/script-archive/playTestSound.js b/script-archive/playTestSound.js index 318df6a257..573c8879c4 100644 --- a/script-archive/playTestSound.js +++ b/script-archive/playTestSound.js @@ -2,12 +2,12 @@ // playTestSound.js // examples // -// Created by Philip Rosedale +// Created by Philip Rosedale // Copyright 2014 High Fidelity, Inc. // -// Creates an object in front of you that changes color and plays a light -// at the start of a drum clip that loops. As you move away it will tell you in the -// log how many meters you are from the source. +// Creates an object in front of you that changes color and plays a light +// at the start of a drum clip that loops. As you move away it will tell you in the +// log how many meters you are from the source. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -17,7 +17,7 @@ var sound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Dru var position = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Quat.getFront(MyAvatar.orientation)); -var time; +var time; var soundPlaying = null; var baseColor = { red: 100, green: 100, blue: 100 }; @@ -38,8 +38,8 @@ var box = Entities.addEntity({ function checkSound(deltaTime) { var started = false; - if (!sound.downloaded) { - return; + if (!sound.downloaded) { + return; } if (soundPlaying == null) { soundPlaying = Audio.playSound(sound, { @@ -47,9 +47,9 @@ function checkSound(deltaTime) { volume: 1.0, loop: false } ); started = true; - } else if (!soundPlaying.isPlaying) { + } else if (!soundPlaying.playing) { soundPlaying.restart(); - started = true; + started = true; } if (started) { Entities.editEntity(box, { color: litColor }); @@ -67,19 +67,19 @@ function checkSound(deltaTime) { lifetime: lightTime / 1000 }); Script.setTimeout(resetColor, lightTime); - } + } var currentDistance = Vec3.distance(MyAvatar.position, position); if (Math.abs(currentDistance - distance) > 1.0) { print("Distance from source: " + currentDistance); distance = currentDistance; - } + } } function resetColor() { Entities.editEntity(box, { color: baseColor }); } - + function scriptEnding() { Entities.deleteEntity(box); if (soundPlaying) { @@ -93,4 +93,3 @@ function scriptEnding() { // Connect a call back that happens every frame Script.scriptEnding.connect(scriptEnding); Script.update.connect(checkSound); - diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index f4e4492a88..ca3b5e8cf2 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -22,10 +22,7 @@ // (For now, the thumb buttons on both controllers are always on.) // When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. // Otherwise, the active hand controller shows a red ball where a click will act. -// -// Bugs: -// On Windows, the upper left corner of Interface must be in the upper left corner of the screen, and the title bar must be 50px high. (System bug.) -// While hardware mouse move switches to mouse move, hardware mouse click (without amove) does not. + // UTILITIES ------------- @@ -269,76 +266,24 @@ function toggleHand() { } } -// Create clickMappings as needed, on demand. -var clickMappings = {}, clickMapping, clickMapToggle; -var hardware; // undefined -function checkHardware() { - var newHardware = Controller.Hardware.Hydra ? 'Hydra' : (Controller.Hardware.Vive ? 'Vive' : null); // not undefined - if (hardware === newHardware) { - return; - } - print('Setting mapping for new controller hardware:', newHardware); - if (clickMapToggle) { - clickMapToggle.setState(false); - } - hardware = newHardware; - if (clickMappings[hardware]) { - clickMapping = clickMappings[hardware]; - } else { - clickMapping = Controller.newMapping(Script.resolvePath('') + '-click-' + hardware); - Script.scriptEnding.connect(clickMapping.disable); - function mapToAction(button, action) { - clickMapping.from(Controller.Hardware[hardware][button]).peek().to(Controller.Actions[action]); - } - function makeHandToggle(button, hand, optionalWhen) { - var whenThunk = optionalWhen || function () { - return true; - }; - function maybeToggle() { - if (activeHand !== Controller.Standard[hand]) { - toggleHand(); - } +var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +Script.scriptEnding.connect(clickMapping.disable); - } - clickMapping.from(Controller.Hardware[hardware][button]).peek().when(whenThunk).to(maybeToggle); - } - function makeViveWhen(click, x, y) { - var viveClick = Controller.Hardware.Vive[click], - viveX = Controller.Standard[x], // Standard after filtering by mapping - viveY = Controller.Standard[y]; - return function () { - var clickValue = Controller.getValue(viveClick); - var xValue = Controller.getValue(viveX); - var yValue = Controller.getValue(viveY); - return clickValue && !xValue && !yValue; - }; - } - switch (hardware) { - case 'Hydra': - makeHandToggle('R3', 'RightHand'); - makeHandToggle('L3', 'LeftHand'); - - mapToAction('R3', 'ReticleClick'); - mapToAction('L3', 'ReticleClick'); - mapToAction('R4', 'ContextMenu'); - mapToAction('L4', 'ContextMenu'); - break; - case 'Vive': - // When touchpad click is NOT treated as movement, treat as left click - makeHandToggle('RS', 'RightHand', makeViveWhen('RS', 'RX', 'RY')); - makeHandToggle('LS', 'LeftHand', makeViveWhen('LS', 'LX', 'LY')); - clickMapping.from(Controller.Hardware.Vive.RS).when(makeViveWhen('RS', 'RX', 'RY')).to(Controller.Actions.ReticleClick); - clickMapping.from(Controller.Hardware.Vive.LS).when(makeViveWhen('LS', 'LX', 'LY')).to(Controller.Actions.ReticleClick); - mapToAction('RightApplicationMenu', 'ContextMenu'); - mapToAction('LeftApplicationMenu', 'ContextMenu'); - break; - } - clickMappings[hardware] = clickMapping; +clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); +clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); +clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(function (on) { + if (on && (activeHand !== Controller.Standard.RightHand)) { + toggleHand(); } - clickMapToggle = new LatchedToggle(clickMapping.enable, clickMapping.disable); - clickMapToggle.setState(true); -} -checkHardware(); +}); +clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(function (on) { + if (on && (activeHand !== Controller.Standard.LeftHand)) { + toggleHand(); + } +}); +clickMapping.enable(); // VISUAL AID ----------- // Same properties as handControllerGrab search sphere @@ -415,8 +360,8 @@ function update() { return turnOffVisualization(); } var controllerPose = Controller.getPoseValue(activeHand); - // Vive is effectively invalid when not in HMD - if (!controllerPose.valid || ((hardware === 'Vive') && !HMD.active)) { + // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) + if (!controllerPose.valid) { return turnOffVisualization(); } // Controller is cradled. var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), @@ -458,7 +403,6 @@ Script.scriptEnding.connect(function () { var SETTINGS_CHANGE_RECHECK_INTERVAL = 10 * 1000; // milliseconds function checkSettings() { updateFieldOfView(); - checkHardware(); } checkSettings(); var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL); diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index afb634ecbd..a796d62ba5 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -54,4 +54,51 @@ void GLMHelpersTests::testEulerDecomposition() { } } +static void testQuatCompression(glm::quat testQuat) { + float MAX_COMPONENT_ERROR = 4.3e-5f; + + glm::quat q; + uint8_t bytes[6]; + packOrientationQuatToSixBytes(bytes, testQuat); + unpackOrientationQuatFromSixBytes(bytes, q); + if (glm::dot(q, testQuat) < 0.0f) { + q = -q; + } + QCOMPARE_WITH_ABS_ERROR(q.x, testQuat.x, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.y, testQuat.y, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.z, testQuat.z, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.w, testQuat.w, MAX_COMPONENT_ERROR); +} + +void GLMHelpersTests::testSixByteOrientationCompression() { + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + testQuatCompression(ROT_X_90); + testQuatCompression(ROT_Y_180); + testQuatCompression(ROT_Z_30); + testQuatCompression(ROT_X_90 * ROT_Y_180); + testQuatCompression(ROT_Y_180 * ROT_X_90); + testQuatCompression(ROT_Z_30 * ROT_X_90); + testQuatCompression(ROT_X_90 * ROT_Z_30); + testQuatCompression(ROT_Z_30 * ROT_Y_180); + testQuatCompression(ROT_Y_180 * ROT_Z_30); + testQuatCompression(ROT_X_90 * ROT_Y_180 * ROT_Z_30); + testQuatCompression(ROT_Y_180 * ROT_Z_30 * ROT_X_90); + testQuatCompression(ROT_Z_30 * ROT_X_90 * ROT_Y_180); + + testQuatCompression(-ROT_X_90); + testQuatCompression(-ROT_Y_180); + testQuatCompression(-ROT_Z_30); + testQuatCompression(-(ROT_X_90 * ROT_Y_180)); + testQuatCompression(-(ROT_Y_180 * ROT_X_90)); + testQuatCompression(-(ROT_Z_30 * ROT_X_90)); + testQuatCompression(-(ROT_X_90 * ROT_Z_30)); + testQuatCompression(-(ROT_Z_30 * ROT_Y_180)); + testQuatCompression(-(ROT_Y_180 * ROT_Z_30)); + testQuatCompression(-(ROT_X_90 * ROT_Y_180 * ROT_Z_30)); + testQuatCompression(-(ROT_Y_180 * ROT_Z_30 * ROT_X_90)); + testQuatCompression(-(ROT_Z_30 * ROT_X_90 * ROT_Y_180)); +} diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index 5e880899e8..40d552a07b 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -19,6 +19,7 @@ class GLMHelpersTests : public QObject { Q_OBJECT private slots: void testEulerDecomposition(); + void testSixByteOrientationCompression(); }; float getErrorDifference(const float& a, const float& b); diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 1745658193..97f0c0a613 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -23,14 +23,17 @@ ApplicationWindow { Desktop { id: desktop anchors.fill: parent - rootMenu: StubMenu { id: rootMenu } + + //rootMenu: StubMenu { id: rootMenu } //Component.onCompleted: offscreenWindow = appWindow + /* MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY)); } + */ Row { id: testButtons