mirror of
https://github.com/lubosz/overte.git
synced 2025-04-14 16:06:13 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into reset-hud-on-driving
This commit is contained in:
commit
9ff1a695f0
79 changed files with 1150 additions and 777 deletions
|
@ -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<NodeList>();
|
||||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);
|
||||
}
|
||||
|
||||
AvatarMixer::~AvatarMixer() {
|
||||
|
@ -414,7 +417,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> 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<NodeList>();
|
||||
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";
|
||||
|
|
|
@ -38,7 +38,8 @@ private slots:
|
|||
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void domainSettingsRequestComplete();
|
||||
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
|
||||
private:
|
||||
void broadcastAvatarData();
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
|
|
@ -55,11 +55,19 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
if (message->getSize() == 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);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <DomainHandler.h>
|
||||
|
||||
#include <NLPacket.h>
|
||||
#include <Node.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<LimitedNodeList>();
|
||||
|
||||
// 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<PacketVersion>(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<Ass
|
|||
}
|
||||
|
||||
void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
||||
|
||||
QDataStream packetStream(message->getMessage());
|
||||
NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false);
|
||||
|
||||
|
|
|
@ -99,6 +99,8 @@ private:
|
|||
|
||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
||||
|
||||
static bool packetVersionMatch(const udt::Packet& packet);
|
||||
|
||||
bool resetAccountManagerAccessToken();
|
||||
|
||||
void setupAutomaticNetworking();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,6 +28,8 @@ public:
|
|||
HifiSockAddr senderSockAddr;
|
||||
QList<NodeType_t> interestList;
|
||||
QString placeName;
|
||||
|
||||
QByteArray protocolVersion;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<DomainHandler::ConnectionRefusedReason>(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>();
|
||||
nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version
|
||||
nodeList->reset();
|
||||
}
|
||||
|
||||
bool Application::askToSetAvatarUrl(const QString& url) {
|
||||
QUrl realUrl(url);
|
||||
if (realUrl.isLocalFile()) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>();
|
||||
accountManager->setSessionID(_sessionID);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<DdeFaceTracker>()->getEyeClosingThreshold(); };
|
||||
auto setter = [](float value) { DependencyManager::get<DdeFaceTracker>()->setEyeClosingThreshold(value); };
|
||||
|
|
|
@ -107,6 +107,18 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const {
|
|||
}
|
||||
}
|
||||
|
||||
void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector<glm::quat>& 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);
|
||||
|
|
|
@ -55,6 +55,8 @@ public:
|
|||
void convertRelativePosesToAbsolute(AnimPoseVec& poses) const;
|
||||
void convertAbsolutePosesToRelative(AnimPoseVec& poses) const;
|
||||
|
||||
void convertAbsoluteRotationsToRelative(std::vector<glm::quat>& rotations) const;
|
||||
|
||||
void mirrorRelativePoses(AnimPoseVec& poses) const;
|
||||
void mirrorAbsolutePoses(AnimPoseVec& poses) const;
|
||||
|
||||
|
|
|
@ -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<AnimSkeleton>(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<AnimSkeleton>(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<JointData>& 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<JointData>& 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<bool> overrideFlags(_internalPoseSet._overridePoses.size(), false);
|
||||
|
||||
// start with the default rotations in absolute rig frame
|
||||
std::vector<glm::quat> 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<glm::vec3> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<unsigned char*>(avatarDataByteArray.data());
|
||||
unsigned char* startPosition = destinationBuffer;
|
||||
|
||||
const glm::vec3& position = getLocalPosition();
|
||||
memcpy(destinationBuffer, &position, sizeof(position));
|
||||
destinationBuffer += sizeof(position);
|
||||
auto header = reinterpret_cast<AvatarDataPacket::Header*>(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<AvatarDataPacket::ParentInfo*>(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<AvatarDataPacket::FaceTrackerInfo*>(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<const unsigned char*>(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<const AvatarDataPacket::Header*>(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<const AvatarDataPacket::ParentInfo*>(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<const AvatarDataPacket::FaceTrackerInfo*>(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<bool> 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<bool> 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> 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();
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ typedef unsigned long long quint64;
|
|||
#include <SimpleMovingAverage.h>
|
||||
#include <SpatiallyNestable.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <Packed.h>
|
||||
|
||||
#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> 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; }
|
||||
|
|
|
@ -50,26 +50,26 @@ AvatarSharedPointer AvatarHashMap::newSharedAvatar() {
|
|||
|
||||
AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& 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<Node>& 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<ReceivedMessage> 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<ReceivedMessage> mess
|
|||
}
|
||||
|
||||
void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> 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> 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<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
@ -148,9 +123,9 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, S
|
|||
|
||||
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) {
|
||||
QWriteLocker locker(&_hashLock);
|
||||
|
||||
|
||||
auto removedAvatar = _avatarHash.take(sessionUUID);
|
||||
|
||||
|
||||
if (removedAvatar) {
|
||||
handleRemovedAvatar(removedAvatar);
|
||||
}
|
||||
|
|
|
@ -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) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
void setBlendshapeCoefficients(const QVector<float>& 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<float> _blendshapeCoefficients;
|
||||
float _pupilDilation;
|
||||
AvatarData* _owningAvatar;
|
||||
|
||||
|
||||
private:
|
||||
// privatize copy ctor and assignment operator so copies of this object cannot be made
|
||||
HeadData(const HeadData&);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AccountManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
|
@ -26,13 +28,13 @@
|
|||
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#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("/")) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ReceivedMessage> 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<ReceivedMessage> message) {
|
||||
// Read deny reason from packet
|
||||
uint8_t reasonCodeWire;
|
||||
|
||||
message->readPrimitive(&reasonCodeWire);
|
||||
ConnectionRefusedReason reasonCode = static_cast<ConnectionRefusedReason>(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<AccountManager>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<udt::BasePacket> packet);
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ int NLPacket::maxPayloadSize(PacketType type, bool isPartOfMessage) {
|
|||
return Packet::maxPayloadSize(isPartOfMessage) - NLPacket::localHeaderSize(type);
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacket> NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) {
|
||||
auto packet = std::unique_ptr<NLPacket>(new NLPacket(type, size, isReliable, isPartOfMessage));
|
||||
std::unique_ptr<NLPacket> NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) {
|
||||
auto packet = std::unique_ptr<NLPacket>(new NLPacket(type, size, isReliable, isPartOfMessage, version));
|
||||
|
||||
packet->open(QIODevice::ReadWrite);
|
||||
|
||||
|
@ -61,13 +61,13 @@ std::unique_ptr<NLPacket> NLPacket::createCopy(const NLPacket& other) {
|
|||
return std::unique_ptr<NLPacket>(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();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH;
|
||||
|
||||
static std::unique_ptr<NLPacket> 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<NLPacket> fromReceivedPacket(std::unique_ptr<char[]> 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<char[]> data, qint64 size, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
NLPacket(const NLPacket& other);
|
||||
|
|
|
@ -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<PacketVersion>(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<AddressManager>()->getPlaceName();
|
||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||
if (_domainConnectRequestVersion >= static_cast<PacketVersion>(DomainConnectRequestVersion::HasHostname)) {
|
||||
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
|
||||
}
|
||||
|
||||
if (!_domainHandler.isConnected()) {
|
||||
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
||||
|
|
|
@ -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<ReceivedMessage> 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
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#include "PacketHeaders.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QMetaEnum>
|
||||
|
||||
|
@ -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<PacketVersion>(AvatarMixerPacketVersion::AvatarEntities);
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(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<PacketVersion>(DomainConnectionDeniedVersion::IncludesReasonCode);
|
||||
|
||||
case PacketType::DomainConnectRequest:
|
||||
// addition of referring hostname information
|
||||
return 18;
|
||||
return static_cast<PacketVersion>(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<uint8_t>(PacketType::LAST_PACKET_TYPE) + 1;
|
||||
stream << numberOfProtocols;
|
||||
for (uint8_t packetType = 0; packetType < numberOfProtocols; packetType++) {
|
||||
uint8_t packetTypeVersion = static_cast<uint8_t>(versionForPacketType(static_cast<PacketType>(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;
|
||||
}
|
||||
|
|
|
@ -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<PacketType> NON_SOURCED_PACKETS;
|
|||
extern const QSet<PacketType> 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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<bool> _isFinished { false };
|
||||
std::atomic<bool> _isRunning { false };
|
||||
std::atomic<bool> _isStopping { false };
|
||||
int _evaluatesPending { 0 };
|
||||
bool _isInitialized { false };
|
||||
QHash<QTimer*, CallbackData> _timerFunctionMap;
|
||||
|
|
|
@ -380,7 +380,7 @@ void ScriptEngines::stopAllScripts(bool restart) {
|
|||
// Stop and possibly restart all currently running scripts
|
||||
for (QHash<QUrl, ScriptEngine*>::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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
12
libraries/shared/src/Packed.h
Normal file
12
libraries/shared/src/Packed.h
Normal file
|
@ -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
|
|
@ -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"),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)],
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class GLMHelpersTests : public QObject {
|
|||
Q_OBJECT
|
||||
private slots:
|
||||
void testEulerDecomposition();
|
||||
void testSixByteOrientationCompression();
|
||||
};
|
||||
|
||||
float getErrorDifference(const float& a, const float& b);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue