Merge branch 'master' of https://github.com/highfidelity/hifi into color

This commit is contained in:
samcake 2016-06-02 10:38:40 -07:00
commit 25eb569433
351 changed files with 11385 additions and 9272 deletions
LICENSE
assignment-client/src
cmake/macros
domain-server/src
interface
libraries

View file

@ -6,7 +6,7 @@ Licensed under the Apache License version 2.0 (the "License");
You may not use this software except in compliance with the License.
You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
This software includes third-party software.
This software includes third-party and other platform software.
Please see each individual software license for additional details.
This software is distributed "as-is" without any warranties, conditions, or representations whether express or implied, including without limitation the implied warranties and conditions of merchantability, merchantable quality, fitness for a particular purpose, performance, durability, title, non-infringement, and those arising from statute or from custom or usage of trade or course of dealing.

View file

@ -51,6 +51,8 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
LogUtils::init();
QSettings::setDefaultFormat(QSettings::IniFormat);
DependencyManager::set<AccountManager>();
auto scriptableAvatar = DependencyManager::set<ScriptableAvatar>();
auto addressManager = DependencyManager::set<AddressManager>();
@ -116,7 +118,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
_requestTimer.start(ASSIGNMENT_REQUEST_INTERVAL_MSECS);
// connections to AccountManager for authentication
connect(&AccountManager::getInstance(), &AccountManager::authRequired,
connect(DependencyManager::get<AccountManager>().data(), &AccountManager::authRequired,
this, &AssignmentClient::handleAuthenticationRequest);
// Create Singleton objects on main thread
@ -309,13 +311,13 @@ void AssignmentClient::handleAuthenticationRequest() {
QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV);
QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV);
AccountManager& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
if (!username.isEmpty() && !password.isEmpty()) {
// ask the account manager to log us in from the env variables
accountManager.requestAccessToken(username, password);
accountManager->requestAccessToken(username, password);
} else {
qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager.getAuthURL().toString())
qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString())
<< "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV)
<< "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate.";

View file

@ -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";

View file

@ -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);

View file

@ -60,8 +60,8 @@ public:
virtual void trackViewerGone(const QUuid& sessionID) override;
public slots:
virtual void nodeAdded(SharedNodePointer node);
virtual void nodeKilled(SharedNodePointer node);
virtual void nodeAdded(SharedNodePointer node) override;
virtual void nodeKilled(SharedNodePointer node) override;
void pruneDeletedEntities();
protected:

View file

@ -153,7 +153,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
qDebug() << " maxSize=" << maxSize;
qDebug("OctreeInboundPacketProcessor::processPacket() %hhu "
"payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d",
packetType, message->getRawMessage(), message->getSize(), editData,
(unsigned char)packetType, message->getRawMessage(), message->getSize(), editData,
message->getPosition(), maxSize);
}
@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
if (debugProcessPacket) {
qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu "
"payload=%p payloadLength=%lld editData=%p payloadPosition=%lld",
packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition());
(unsigned char)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition());
}
// Make sure our Node and NodeList knows we've heard from this node.
@ -208,7 +208,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
}
trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime);
} else {
qDebug("unknown packet ignored... packetType=%hhu", packetType);
qDebug("unknown packet ignored... packetType=%hhu", (unsigned char)packetType);
}
}

View file

@ -7,18 +7,21 @@
#
macro(TARGET_NSIGHT)
if (WIN32 AND USE_NSIGHT)
# grab the global CHECKED_FOR_NSIGHT_ONCE property
get_property(NSIGHT_CHECKED GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE)
get_property(NSIGHT_UNAVAILABLE GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE)
if (NOT NSIGHT_CHECKED)
if (NOT NSIGHT_UNAVAILABLE)
# try to find the Nsight package and add it to the build if we find it
find_package(NSIGHT)
# set the global CHECKED_FOR_NSIGHT_ONCE property so that we only debug that we couldn't find it once
set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE)
# Cache the failure to find nsight, so that we don't check over and over
if (NOT NSIGHT_FOUND)
set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE)
endif()
endif ()
# try to find the Nsight package and add it to the build if we find it
if (NSIGHT_FOUND)
include_directories(${NSIGHT_INCLUDE_DIRS})
add_definitions(-DNSIGHT_FOUND)

View file

@ -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.";
@ -105,6 +113,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
nodeData->setSendingSockAddr(message->getSenderSockAddr());
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
nodeData->setPlaceName(nodeConnection.placeName);
// signal that we just connected a node so the DomainServer can get it a list
// and broadcast its presence right away
@ -150,6 +159,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
nodeData->setWalletUUID(it->second.getWalletUUID());
nodeData->setNodeVersion(it->second.getNodeVersion());
nodeData->setWasAssigned(true);
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
_pendingAssignedNodes.erase(it);
@ -282,7 +292,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
// set the edit rights for this user
newNode->setIsAllowedEditor(isAllowedEditor);
newNode->setCanRez(canRez);
// grab the linked data for our new node so we can set the username
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
@ -330,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);
@ -368,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
@ -380,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);
}
}
@ -400,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;
}
@ -414,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;
}
@ -428,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
@ -450,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;
}
@ -485,7 +501,7 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
qDebug() << "Requesting public key for user" << username;
AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
DependencyManager::get<AccountManager>()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
AccountManagerAuth::None,
QNetworkAccessManager::GetOperation, callbackParams);
}
@ -514,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);
}

View file

@ -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);

View file

@ -26,6 +26,7 @@
#include <AccountManager.h>
#include <BuildInfo.h>
#include <DependencyManager.h>
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
#include <LogUtils.h>
@ -77,7 +78,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// make sure we have a fresh AccountManager instance
// (need this since domain-server can restart itself and maintain static variables)
AccountManager::getInstance(true);
DependencyManager::set<AccountManager>();
auto args = arguments();
@ -195,8 +196,8 @@ bool DomainServer::optionallySetupOAuth() {
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL;
}
AccountManager& accountManager = AccountManager::getInstance();
accountManager.setAuthURL(_oauthProviderURL);
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->setAuthURL(_oauthProviderURL);
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
@ -239,7 +240,7 @@ void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) {
// we've been asked to grab a temporary name from the API
// so fire off that request now
auto& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
// get callbacks for temporary domain result
JSONCallbackParameters callbackParameters;
@ -248,8 +249,8 @@ void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) {
callbackParameters.errorCallbackReceiver = this;
callbackParameters.errorCallbackMethod = "handleTempDomainError";
accountManager.sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParameters);
accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParameters);
}
}
@ -302,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";
@ -375,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";
@ -397,7 +426,7 @@ bool DomainServer::resetAccountManagerAccessToken() {
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
// clear any existing access token from AccountManager
AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString());
DependencyManager::get<AccountManager>()->setAccessTokenForCurrentAuthURL(QString());
return false;
}
@ -407,7 +436,7 @@ bool DomainServer::resetAccountManagerAccessToken() {
}
// give this access token to the AccountManager
AccountManager::getInstance().setAccessTokenForCurrentAuthURL(accessToken);
DependencyManager::get<AccountManager>()->setAccessTokenForCurrentAuthURL(accessToken);
return true;
@ -461,7 +490,7 @@ void DomainServer::setupAutomaticNetworking() {
nodeList->startSTUNPublicSocketUpdate();
} else {
// send our heartbeat to data server so it knows what our network settings are
sendHeartbeatToDataServer();
sendHeartbeatToMetaverse();
}
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
@ -470,7 +499,7 @@ void DomainServer::setupAutomaticNetworking() {
return;
}
} else {
sendHeartbeatToDataServer();
sendHeartbeatToMetaverse();
}
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
@ -479,7 +508,7 @@ void DomainServer::setupAutomaticNetworking() {
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
QTimer* dataHeartbeatTimer = new QTimer(this);
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer()));
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
}
@ -499,17 +528,17 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
limitedNodeList->startSTUNPublicSocketUpdate();
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
auto& accountManager = AccountManager::getInstance();
auto domainID = accountManager.getAccountInfo().getDomainID();
auto accountManager = DependencyManager::get<AccountManager>();
auto domainID = accountManager->getAccountInfo().getDomainID();
// if we have an access token and we don't have a private key or the current domain ID has changed
// we should generate a new keypair
if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
}
// hookup to the signal from account manager that tells us when keypair is available
connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
if (!_iceHeartbeatTimer) {
// setup a timer to heartbeat with the ice-server every so often
@ -665,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);
@ -677,6 +706,9 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet());
// update the connecting hostname in case it has changed
nodeData->setPlaceName(nodeRequestData.placeName);
sendDomainListToNode(sendingNode, message->getSenderSockAddr());
}
@ -962,9 +994,9 @@ void DomainServer::setupPendingAssignmentCredits() {
void DomainServer::sendPendingTransactionsToServer() {
AccountManager& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager.hasValidAccessToken()) {
if (accountManager->hasValidAccessToken()) {
// enumerate the pending transactions and send them to the server to complete payment
TransactionHash::iterator i = _pendingAssignmentCredits.begin();
@ -975,7 +1007,7 @@ void DomainServer::sendPendingTransactionsToServer() {
transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback";
while (i != _pendingAssignmentCredits.end()) {
accountManager.sendRequest("api/v1/transactions",
accountManager->sendRequest("api/v1/transactions",
AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation,
transactionCallbackParams, i.value()->postJson().toJson());
@ -1028,11 +1060,11 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString());
sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString());
}
void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
auto nodeList = DependencyManager::get<LimitedNodeList>();
@ -1055,25 +1087,39 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
domainObject[RESTRICTED_ACCESS_FLAG] =
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;
// figure out the breakdown of currently connected interface clients
int numConnectedUnassigned = 0;
QJsonObject userHostnames;
nodeList->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){
if (node->getLinkedData() && !static_cast<DomainServerNodeData*>(node->getLinkedData())->getUsername().isEmpty()) {
++numConnectedAuthedUsers;
static const QString DEFAULT_HOSTNAME = "*";
nodeList->eachNode([&numConnectedUnassigned, &userHostnames](const SharedNodePointer& node) {
if (node->getLinkedData()) {
auto nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->wasAssigned()) {
++numConnectedUnassigned;
// increment the count for this hostname (or the default if we don't have one)
auto hostname = nodeData->getPlaceName().isEmpty() ? DEFAULT_HOSTNAME : nodeData->getPlaceName();
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
}
}
});
const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
static const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
static const QString HEARTBEAT_USER_HOSTNAMES_KEY = "user_hostnames";
QJsonObject heartbeatObject;
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers;
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedUnassigned;
heartbeatObject[HEARTBEAT_USER_HOSTNAMES_KEY] = userHostnames;
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
AccountManager::getInstance().sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
AccountManagerAuth::Required,
QNetworkAccessManager::PutOperation,
JSONCallbackParameters(),
@ -1103,7 +1149,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
AccountManager::getInstance().sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
AccountManagerAuth::Optional,
QNetworkAccessManager::PutOperation,
callbackParameters,
@ -1123,15 +1169,15 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestRepl
void DomainServer::sendHeartbeatToIceServer() {
if (!_iceServerSocket.getAddress().isNull()) {
auto& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
if (!accountManager.getAccountInfo().hasPrivateKey()) {
if (!accountManager->getAccountInfo().hasPrivateKey()) {
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
if (!limitedNodeList->getSessionUUID().isNull()) {
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
} else {
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
}
@ -1208,7 +1254,7 @@ void DomainServer::sendHeartbeatToIceServer() {
auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize());
// generate a signature for the plaintext data in the packet
auto signature = accountManager.getAccountInfo().signPlaintext(plaintext);
auto signature = accountManager->getAccountInfo().signPlaintext(plaintext);
// pack the signature with the data
heartbeatDataStream << signature;
@ -2101,7 +2147,7 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<Received
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID());
DependencyManager::get<AccountManager>()->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
// reset our number of heartbeat denials
_numHeartbeatDenials = 0;

View file

@ -71,7 +71,7 @@ private slots:
void sendPendingTransactionsToServer();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); }
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
void sendHeartbeatToIceServer();
void handleConnectedNode(SharedNodePointer newNode);
@ -99,11 +99,13 @@ private:
void optionallyGetTemporaryName(const QStringList& arguments);
static bool packetVersionMatch(const udt::Packet& packet);
bool resetAccountManagerAccessToken();
void setupAutomaticNetworking();
void setupICEHeartbeatForFullNetworking();
void sendHeartbeatToDataServer(const QString& networkAddress);
void sendHeartbeatToMetaverse(const QString& networkAddress);
void randomizeICEServerAddress(bool shouldTriggerHostLookup);

View file

@ -56,6 +56,12 @@ public:
void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue);
void removeOverrideForKey(const QString& key, const QString& value);
const QString& getPlaceName() { return _placeName; }
void setPlaceName(const QString& placeName) { _placeName = placeName; }
bool wasAssigned() const { return _wasAssigned; };
void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; }
private:
QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats);
@ -75,6 +81,10 @@ private:
bool _isAuthenticated = true;
NodeSet _nodeInterestSet;
QString _nodeVersion;
QString _placeName;
bool _wasAssigned { false };
};
#endif // hifi_DomainServerNodeData_h

View file

@ -19,11 +19,21 @@ 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
>> newHeader.publicSockAddr >> newHeader.localSockAddr
>> newHeader.interestList;
>> newHeader.interestList >> newHeader.placeName;
newHeader.senderSockAddr = senderSockAddr;

View file

@ -27,6 +27,9 @@ public:
HifiSockAddr localSockAddr;
HifiSockAddr senderSockAddr;
QList<NodeType_t> interestList;
QString placeName;
QByteArray protocolVersion;
};

View file

@ -4,10 +4,6 @@ project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "LeapMotion")
if(WIN32)
list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient")
endif()
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
@ -139,7 +135,7 @@ if (WIN32)
endif()
# link required hifi libraries
link_hifi_libraries(shared octree gpu gl procedural model render
link_hifi_libraries(shared octree gpu gl gpu-gl procedural model render
recording fbx networking model-networking entities avatars
audio audio-client animation script-engine physics
render-utils entities-renderer ui auto-updater

View file

@ -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" }

View file

@ -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" }
]
}

View file

@ -2,11 +2,11 @@
"name": "Spacemouse to Standard",
"channels": [
{ "from": "Spacemouse.TranslateX", "to": "Standard.RX" },
{ "from": "Spacemouse.TranslateX", "to": "Standard.LX" },
{ "from": "Spacemouse.TranslateY", "to": "Standard.LY" },
{ "from": "Spacemouse.TranslateZ", "to": "Standard.RY" },
{ "from": "Spacemouse.RotateZ", "to": "Standard.LX" },
{ "from": "Spacemouse.RotateZ", "to": "Standard.RX" },
{ "from": "Spacemouse.LeftButton", "to": "Standard.LB" },
{ "from": "Spacemouse.RightButton", "to": "Standard.RB" }

View file

@ -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" }

Binary file not shown.

View file

@ -21,7 +21,6 @@ import "../hifi/models"
TableView {
id: tableView
// property var tableModel: ListModel { }
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
@ -46,7 +45,7 @@ TableView {
RalewayRegular {
id: textHeader
size: hifi.fontSizes.tableText
size: hifi.fontSizes.tableHeading
color: hifi.colors.lightGrayText
text: styleData.value
anchors {
@ -87,7 +86,7 @@ TableView {
bottomMargin: 3 // ""
}
radius: 3
color: hifi.colors.tableScrollHandle
color: hifi.colors.tableScrollHandleDark
}
}
@ -107,7 +106,7 @@ TableView {
margins: 1 // Shrink
}
radius: 4
color: hifi.colors.tableScrollBackground
color: hifi.colors.tableScrollBackgroundDark
}
}

View file

@ -91,7 +91,7 @@ FocusScope {
HiFiGlyphs {
anchors {
top: parent.top
topMargin: -8
topMargin: -11
horizontalCenter: parent.horizontalCenter
}
size: hifi.dimensions.spinnerSize

View file

@ -17,8 +17,9 @@ import "../styles-uit"
Original.Button {
property int color: 0
property int colorScheme: hifi.colorShemes.light
property int colorScheme: hifi.colorSchemes.light
property string glyph: ""
property int size: 32
width: 120
height: 28
@ -65,7 +66,13 @@ Original.Button {
: hifi.buttons.disabledTextColor[control.colorScheme]
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors {
// Tweak horizontal alignment so that it looks right.
left: parent.left
leftMargin: -0.5
}
text: control.glyph
size: control.size
}
}
}

View file

@ -56,7 +56,7 @@ SpinBox {
incrementControl: HiFiGlyphs {
id: incrementButton
text: hifi.glyphs.caratUp
x: 6
x: 10
y: 1
size: hifi.dimensions.spinnerSize
color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
@ -64,8 +64,8 @@ SpinBox {
decrementControl: HiFiGlyphs {
text: hifi.glyphs.caratDn
x: 6
y: -3
x: 10
y: -1
size: hifi.dimensions.spinnerSize
color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
}

View file

@ -17,20 +17,68 @@ import "../styles-uit"
TableView {
id: tableView
property var tableModel: ListModel { }
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
property bool expandSelectedRow: false
model: tableModel
TableViewColumn {
role: "name"
}
anchors { left: parent.left; right: parent.right }
model: ListModel { }
headerVisible: false
headerDelegate: Item { } // Fix OSX QML bug that displays scrollbar starting too low.
headerDelegate: Rectangle {
height: hifi.dimensions.tableHeaderHeight
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
RalewayRegular {
id: titleText
text: styleData.value
size: hifi.fontSizes.tableHeading
font.capitalization: Font.AllUppercase
color: hifi.colors.baseGrayHighlight
anchors {
left: parent.left
leftMargin: hifi.dimensions.tablePadding
verticalCenter: parent.verticalCenter
}
}
HiFiGlyphs {
id: titleSort
text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
color: hifi.colors.baseGrayHighlight
size: hifi.fontSizes.tableHeadingIcon
anchors {
left: titleText.right
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3
right: parent.right
rightMargin: hifi.dimensions.tablePadding
verticalCenter: titleText.verticalCenter
}
visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column
}
Rectangle {
width: 1
anchors {
left: parent.left
top: parent.top
topMargin: 1
bottom: parent.bottom
bottomMargin: 2
}
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
visible: styleData.column > 0
}
Rectangle {
height: 1
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
}
}
// Use rectangle to draw border with rounded corners.
frameVisible: false
@ -50,8 +98,10 @@ TableView {
style: TableViewStyle {
// Needed in order for rows to keep displaying rows after end of table entries.
backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven
alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0
handle: Item {
id: scrollbarHandle
@ -59,33 +109,38 @@ TableView {
Rectangle {
anchors {
fill: parent
topMargin: 3
bottomMargin: 3 // ""
leftMargin: 2 // Move it right
rightMargin: -2 // ""
topMargin: 3 // Shrink vertically
bottomMargin: 3 // ""
}
radius: 3
color: hifi.colors.tableScrollHandle
color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark
}
}
scrollBarBackground: Item {
implicitWidth: 10
implicitWidth: 9
Rectangle {
anchors {
fill: parent
margins: -1 // Expand
topMargin: headerVisible ? -hifi.dimensions.tableHeaderHeight : -1
}
color: hifi.colors.baseGrayHighlight
}
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
Rectangle {
anchors {
fill: parent
margins: 1 // Shrink
Rectangle {
// Extend header bottom border
anchors {
top: parent.top
topMargin: hifi.dimensions.tableHeaderHeight - 1
left: parent.left
right: parent.right
}
height: 1
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
visible: headerVisible
}
radius: 4
color: hifi.colors.tableScrollBackground
}
}
@ -99,85 +154,11 @@ TableView {
}
rowDelegate: Rectangle {
height: (styleData.selected ? 1.8 : 1) * hifi.dimensions.tableRowHeight
height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight
color: styleData.selected
? hifi.colors.primaryHighlight
: tableView.isLightColorScheme
? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd)
: (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
}
itemDelegate: Item {
anchors {
left: parent ? parent.left : undefined
leftMargin: hifi.dimensions.tablePadding
right: parent ? parent.right : undefined
rightMargin: hifi.dimensions.tablePadding
}
FiraSansSemiBold {
id: textItem
text: styleData.value
size: hifi.fontSizes.tableText
color: colorScheme == hifi.colorSchemes.light
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
anchors {
left: parent.left
right: parent.right
top: parent.top
topMargin: 3
}
// FIXME: Put reload item in tableModel passed in from RunningScripts.
HiFiGlyphs {
id: reloadButton
text: hifi.glyphs.reloadSmall
color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
anchors {
top: parent.top
right: stopButton.left
verticalCenter: parent.verticalCenter
}
MouseArea {
id: reloadButtonArea
anchors { fill: parent; margins: -2 }
onClicked: reloadScript(model.url)
}
}
// FIXME: Put stop item in tableModel passed in from RunningScripts.
HiFiGlyphs {
id: stopButton
text: hifi.glyphs.closeSmall
color: stopButtonArea.pressed ? hifi.colors.white : parent.color
anchors {
top: parent.top
right: parent.right
verticalCenter: parent.verticalCenter
}
MouseArea {
id: stopButtonArea
anchors { fill: parent; margins: -2 }
onClicked: stopScript(model.url)
}
}
}
// FIXME: Automatically use aux. information from tableModel
FiraSansSemiBold {
text: tableModel.get(styleData.row) ? tableModel.get(styleData.row).url : ""
elide: Text.ElideMiddle
size: hifi.fontSizes.tableText
color: colorScheme == hifi.colorSchemes.light
? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray)
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
anchors {
top: textItem.bottom
left: parent.left
right: parent.right
}
visible: styleData.selected
}
}
}

View file

@ -1,5 +1,5 @@
//
// Table.qml
// Tree.qml
//
// Created by David Rowe on 17 Feb 2016
// Copyright 2016 High Fidelity, Inc.
@ -85,27 +85,18 @@ TreeView {
bottomMargin: 3 // ""
}
radius: 3
color: hifi.colors.tableScrollHandle
color: hifi.colors.tableScrollHandleDark
}
}
scrollBarBackground: Item {
implicitWidth: 10
implicitWidth: 9
Rectangle {
anchors {
fill: parent
margins: -1 // Expand
}
color: hifi.colors.baseGrayHighlight
}
Rectangle {
anchors {
fill: parent
margins: 1 // Shrink
}
radius: 4
color: hifi.colors.tableScrollBackground
color: hifi.colors.tableBackgroundDark
}
}

View file

@ -1,4 +1,14 @@
import QtQuick 2.0
//
// FileDialog.qml
//
// Created by Bradley Austin Davis on 14 Jan 2016
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import Qt.labs.folderlistmodel 2.1
import Qt.labs.settings 1.0
@ -6,17 +16,23 @@ import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import ".."
import "../windows"
import "../styles"
import "../controls" as VrControls
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "fileDialog"
//FIXME implement shortcuts for favorite location
ModalWindow {
id: root
resizable: true
width: 640
height: 480
implicitWidth: 640
implicitHeight: 480
minSize: Qt.vector2d(300, 240)
draggable: true
HifiConstants { id: hifi }
Settings {
category: "FileDialog"
@ -30,12 +46,14 @@ ModalWindow {
// Set from OffscreenUi::getOpenFile()
property alias caption: root.title;
// Set from OffscreenUi::getOpenFile()
property alias dir: model.folder;
property alias dir: fileTableModel.folder;
// Set from OffscreenUi::getOpenFile()
property alias filter: selectionType.filtersString;
// Set from OffscreenUi::getOpenFile()
property int options; // <-- FIXME unused
property string iconText: text !== "" ? hifi.glyphs.scriptUpload : ""
property int iconSize: 40
property bool selectDirectory: false;
property bool showHidden: false;
@ -46,77 +64,120 @@ ModalWindow {
property alias model: fileTableView.model
property var drives: helper.drives()
property int titleWidth: 0
signal selectedFile(var file);
signal canceled();
Component.onCompleted: {
console.log("Helper " + helper + " drives " + drives)
drivesSelector.onCurrentTextChanged.connect(function(){
root.dir = helper.pathToUrl(drivesSelector.currentText);
})
// HACK: The following lines force the model to initialize properly such that the go-up button
// works properly from the initial screen.
var initialFolder = folderListModel.folder;
fileTableModel.folder = helper.pathToUrl(drives[0]);
fileTableModel.folder = initialFolder;
iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";
}
Rectangle {
anchors.fill: parent
color: "white"
Item {
clip: true
width: pane.width
height: pane.height
anchors.margins: 0
Row {
id: navControls
anchors { left: parent.left; top: parent.top; margins: 8 }
spacing: 8
// FIXME implement back button
//VrControls.ButtonAwesome {
// id: backButton
// text: "\uf0a8"
// size: currentDirectory.height
// enabled: d.backStack.length != 0
// MouseArea { anchors.fill: parent; onClicked: d.navigateBack() }
//}
VrControls.ButtonAwesome {
anchors {
top: parent.top
topMargin: hifi.dimensions.contentMargin.y
left: parent.left
}
spacing: hifi.dimensions.contentSpacing.x
GlyphButton {
id: upButton
enabled: model.parentFolder && model.parentFolder !== ""
text: "\uf0aa"
size: 32
glyph: hifi.glyphs.levelUp
width: height
size: 30
enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
onClicked: d.navigateUp();
}
VrControls.ButtonAwesome {
GlyphButton {
id: homeButton
property var destination: helper.home();
glyph: hifi.glyphs.home
size: 28
width: height
enabled: d.homeDestination ? true : false
text: "\uf015"
size: 32
onClicked: d.navigateHome();
}
VrControls.ComboBox {
id: drivesSelector
width: 48
height: homeButton.height
model: drives
visible: drives.length > 1
currentIndex: 0
}
}
TextField {
id: currentDirectory
height: homeButton.height
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 }
property var lastValidFolder: helper.urlToPath(model.folder)
onLastValidFolderChanged: text = lastValidFolder;
verticalAlignment: Text.AlignVCenter
font.pointSize: 14
font.bold: true
ComboBox {
id: pathSelector
anchors {
top: parent.top
topMargin: hifi.dimensions.contentMargin.y
left: navControls.right
leftMargin: hifi.dimensions.contentSpacing.x
right: parent.right
}
// FIXME add support auto-completion
onAccepted: {
if (!helper.validFolder(text)) {
text = lastValidFolder;
return
property var lastValidFolder: helper.urlToPath(fileTableModel.folder)
function calculatePathChoices(folder) {
var folders = folder.split("/"),
choices = [],
i, length;
if (folders[folders.length - 1] === "") {
folders.pop();
}
choices.push(folders[0]);
for (i = 1, length = folders.length; i < length; i++) {
choices.push(choices[i - 1] + "/" + folders[i]);
}
if (folders[0] === "") {
// Special handling for OSX root dir.
choices[0] = "/";
}
choices.reverse();
if (drives && drives.length > 1) {
choices.push("This PC");
}
if (choices.length > 0) {
pathSelector.model = choices;
}
}
onLastValidFolderChanged: {
var folder = d.capitalizeDrive(lastValidFolder);
calculatePathChoices(folder);
}
onCurrentTextChanged: {
var folder = currentText;
if (/^[a-zA-z]:$/.test(folder)) {
folder = "file:///" + folder + "/";
} else if (folder === "This PC") {
folder = "file:///";
} else {
folder = helper.pathToUrl(folder);
}
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
fileTableModel.folder = folder;
}
model.folder = helper.pathToUrl(text);
}
}
@ -127,67 +188,323 @@ ModalWindow {
property bool currentSelectionIsFolder;
property var backStack: []
property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
property var modelConnection: Connections { target: model; onFolderChanged: d.update(); }
property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
property var homeDestination: helper.home();
Component.onCompleted: update();
function capitalizeDrive(path) {
// Consistently capitalize drive letter for Windows.
if (/[a-zA-Z]:/.test(path)) {
return path.charAt(0).toUpperCase() + path.slice(1);
}
return path;
}
function update() {
var row = fileTableView.currentRow;
if (row === -1 && root.selectDirectory) {
currentSelectionUrl = fileTableView.model.folder;
currentSelectionIsFolder = true;
if (row === -1) {
return;
}
currentSelectionUrl = fileTableView.model.get(row, "fileURL");
currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
currentSelectionIsFolder = fileTableView.model.isFolder(row);
if (root.selectDirectory || !currentSelectionIsFolder) {
currentSelection.text = helper.urlToPath(currentSelectionUrl);
currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
} else {
currentSelection.text = ""
currentSelection.text = "";
}
}
function navigateUp() {
if (model.parentFolder && model.parentFolder !== "") {
model.folder = model.parentFolder
if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
fileTableModel.folder = fileTableModel.parentFolder;
return true;
}
}
function navigateHome() {
model.folder = homeDestination;
fileTableModel.folder = homeDestination;
return true;
}
}
FileTableView {
FolderListModel {
id: folderListModel
nameFilters: selectionType.currentFilter
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory
Component.onCompleted: {
showFiles = !root.selectDirectory
}
onFolderChanged: {
fileTableModel.update(); // Update once the data from the folder change is available.
}
function getItem(index, field) {
return get(index, field);
}
}
ListModel {
// Emulates FolderListModel but contains drive data.
id: driveListModel
property int count: 1
Component.onCompleted: initialize();
function initialize() {
var drive,
i;
count = drives.length;
for (i = 0; i < count; i++) {
drive = drives[i].slice(0, -1); // Remove trailing "/".
append({
fileName: drive,
fileModified: new Date(0),
fileSize: 0,
filePath: drive + "/",
fileIsDir: true,
fileNameSort: drive.toLowerCase()
});
}
}
function getItem(index, field) {
return get(index)[field];
}
}
ListModel {
id: fileTableModel
// FolderListModel has a couple of problems:
// 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
// 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
//
// To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
// drive information when viewing at the computer level.
property var folder
property int sortOrder: Qt.AscendingOrder
property int sortColumn: 0
property var model: folderListModel
property string parentFolder: calculateParentFolder();
readonly property string rootFolder: "file:///"
function calculateParentFolder() {
if (model === folderListModel) {
if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
return rootFolder;
} else {
return folderListModel.parentFolder;
}
} else {
return "";
}
}
onFolderChanged: {
if (folder === rootFolder) {
model = driveListModel;
update();
} else {
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
model = folderListModel;
folderListModel.folder = folder;
if (needsUpdate) {
update();
}
}
}
function isFolder(row) {
if (row === -1) {
return false;
}
return get(row).fileIsDir;
}
function update() {
var dataFields = ["fileName", "fileModified", "fileSize"],
sortFields = ["fileNameSort", "fileModified", "fileSize"],
dataField = dataFields[sortColumn],
sortField = sortFields[sortColumn],
sortValue,
fileName,
fileIsDir,
comparisonFunction,
lower,
middle,
upper,
rows = 0,
i;
clear();
comparisonFunction = sortOrder === Qt.AscendingOrder
? function(a, b) { return a < b; }
: function(a, b) { return a > b; }
for (i = 0; i < model.count; i++) {
fileName = model.getItem(i, "fileName");
fileIsDir = model.getItem(i, "fileIsDir");
sortValue = model.getItem(i, dataField);
if (dataField === "fileName") {
// Directories first by prefixing a "*".
// Case-insensitive.
sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
}
lower = 0;
upper = rows;
while (lower < upper) {
middle = Math.floor((lower + upper) / 2);
var lessThan;
if (comparisonFunction(sortValue, get(middle)[sortField])) {
lessThan = true;
upper = middle;
} else {
lessThan = false;
lower = middle + 1;
}
}
insert(lower, {
fileName: fileName,
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
fileSize: model.getItem(i, "fileSize"),
filePath: model.getItem(i, "filePath"),
fileIsDir: fileIsDir,
fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
});
rows++;
}
}
}
Table {
id: fileTableView
anchors { left: parent.left; right: parent.right; top: currentDirectory.bottom; bottom: currentSelection.top; margins: 8 }
colorScheme: hifi.colorSchemes.light
anchors {
top: navControls.bottom
topMargin: hifi.dimensions.contentSpacing.y
left: parent.left
right: parent.right
bottom: currentSelection.top
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
}
headerVisible: !selectDirectory
onDoubleClicked: navigateToRow(row);
focus: true
Keys.onReturnPressed: navigateToCurrentRow();
Keys.onEnterPressed: navigateToCurrentRow();
model: FolderListModel {
id: model
nameFilters: selectionType.currentFilter
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory
// For some reason, declaring these bindings directly in the targets doesn't
// work for setting the initial state
Component.onCompleted: {
currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(model.folder); });
upButton.enabled = Qt.binding(function() { return (model.parentFolder && model.parentFolder != "") ? true : false; });
showFiles = !root.selectDirectory
sortIndicatorColumn: 0
sortIndicatorOrder: Qt.AscendingOrder
sortIndicatorVisible: true
model: fileTableModel
function updateSort() {
model.sortOrder = sortIndicatorOrder;
model.sortColumn = sortIndicatorColumn;
model.update();
}
onSortIndicatorColumnChanged: { updateSort(); }
onSortIndicatorOrderChanged: { updateSort(); }
onActiveFocusChanged: {
if (activeFocus && currentRow == -1) {
fileTableView.selection.select(0)
}
onFolderChanged: {
fileTableView.selection.clear();
fileTableView.selection.select(0);
fileTableView.currentRow = 0;
}
itemDelegate: Item {
clip: true
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
FiraSansSemiBold {
text: getText();
elide: styleData.elideMode
anchors {
left: parent.left
leftMargin: hifi.dimensions.tablePadding
right: parent.right
rightMargin: hifi.dimensions.tablePadding
verticalCenter: parent.verticalCenter
}
size: hifi.fontSizes.tableText
color: hifi.colors.baseGrayHighlight
font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
? firaSansSemiBold.name : firaSansRegular.name
function getText() {
if (styleData.row === -1) {
return styleData.value;
}
switch (styleData.column) {
case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
default: return styleData.value;
}
}
function formatSize(size) {
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
var suffixIndex = 0
while ((size / 1024.0) > 1.1) {
size /= 1024.0;
++suffixIndex;
}
size = Math.round(size*1000)/1000;
size = size.toLocaleString()
return size + " " + suffixes[suffixIndex];
}
}
}
TableViewColumn {
id: fileNameColumn
role: "fileName"
title: "Name"
width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
movable: false
resizable: true
}
TableViewColumn {
id: fileMofifiedColumn
role: "fileModified"
title: "Date"
width: 0.3 * fileTableView.width
movable: false
resizable: true
visible: !selectDirectory
}
TableViewColumn {
role: "fileSize"
title: "Size"
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
movable: false
resizable: true
visible: !selectDirectory
}
function navigateToRow(row) {
currentRow = row;
navigateToCurrentRow();
@ -196,9 +513,9 @@ ModalWindow {
function navigateToCurrentRow() {
var row = fileTableView.currentRow
var isFolder = model.isFolder(row);
var file = model.get(row, "fileURL");
var file = model.get(row).filePath;
if (isFolder) {
fileTableView.model.folder = file
fileTableView.model.folder = helper.pathToUrl(file);
} else {
okAction.trigger();
}
@ -213,7 +530,7 @@ ModalWindow {
var newPrefix = prefix + event.text.toLowerCase();
var matchedIndex = -1;
for (var i = 0; i < model.count; ++i) {
var name = model.get(i, "fileName").toLowerCase();
var name = model.get(i).fileName.toLowerCase();
if (0 === name.indexOf(newPrefix)) {
matchedIndex = i;
break;
@ -254,14 +571,19 @@ ModalWindow {
}
break;
}
}
}
TextField {
id: currentSelection
style: TextFieldStyle { renderType: Text.QtRendering }
anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top }
label: "Path:"
anchors {
left: parent.left
right: selectionType.visible ? selectionType.left: parent.right
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
bottom: buttonRow.top
bottomMargin: hifi.dimensions.contentSpacing.y
}
readOnly: !root.saveDialog
activeFocusOnTab: !readOnly
onActiveFocusChanged: if (activeFocus) { selectAll(); }
@ -270,27 +592,34 @@ ModalWindow {
FileTypeSelection {
id: selectionType
anchors { bottom: buttonRow.top; bottomMargin: 8; right: parent.right; rightMargin: 8; left: buttonRow.left }
visible: !selectDirectory
anchors {
top: currentSelection.top
left: buttonRow.left
right: parent.right
}
visible: !selectDirectory && filtersCount > 1
KeyNavigation.left: fileTableView
KeyNavigation.right: openButton
}
Row {
id: buttonRow
anchors.right: parent.right
anchors.rightMargin: 8
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
spacing: 8
anchors {
right: parent.right
bottom: parent.bottom
}
spacing: hifi.dimensions.contentSpacing.y
Button {
id: openButton
color: hifi.buttons.blue
action: okAction
Keys.onReturnPressed: okAction.trigger()
KeyNavigation.up: selectionType
KeyNavigation.left: selectionType
KeyNavigation.right: cancelButton
}
Button {
id: cancelButton
action: cancelAction
@ -385,5 +714,3 @@ ModalWindow {
}
}
}

View file

@ -1,64 +0,0 @@
import QtQuick 2.0
import QtQuick.Controls 1.4
TableView {
id: root
onActiveFocusChanged: {
if (activeFocus && currentRow == -1) {
root.selection.select(0)
}
}
itemDelegate: Component {
Item {
clip: true
Text {
x: 3
anchors.verticalCenter: parent.verticalCenter
color: styleData.textColor
elide: styleData.elideMode
text: getText();
font.italic: root.model.get(styleData.row, "fileIsDir") ? true : false
function getText() {
switch (styleData.column) {
//case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss");
case 2: return root.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value);
default: return styleData.value;
}
}
function formatSize(size) {
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
var suffixIndex = 0
while ((size / 1024.0) > 1.1) {
size /= 1024.0;
++suffixIndex;
}
size = Math.round(size*1000)/1000;
size = size.toLocaleString()
return size + " " + suffixes[suffixIndex];
}
}
}
}
TableViewColumn {
role: "fileName"
title: "Name"
width: 400
}
TableViewColumn {
role: "fileModified"
title: "Date Modified"
width: 200
}
TableViewColumn {
role: "fileSize"
title: "Size"
width: 200
}
}

View file

@ -1,11 +1,22 @@
//
// FileTypeSelection.qml
//
// Created by Bradley Austin Davis on 29 Jan 2016
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "../../controls" as VrControls
import "../../controls-uit"
VrControls.ComboBox {
ComboBox {
id: root
property string filtersString: "All Files (*.*)";
property var currentFilter: [ "*.*" ];
property int filtersCount: filtersString.split(';;').length
// Per http://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName the string can contain
// multiple filters separated by semicolons

View file

@ -1,166 +0,0 @@
import QtQuick 2.5
QtObject {
readonly property string aboutApp: "About Interface";
readonly property string addRemoveFriends: "Add/Remove Friends...";
readonly property string addressBar: "Show Address Bar";
readonly property string animations: "Animations...";
readonly property string animDebugDrawAnimPose: "Debug Draw Animation";
readonly property string animDebugDrawDefaultPose: "Debug Draw Default Pose";
readonly property string animDebugDrawPosition: "Debug Draw Position";
readonly property string antialiasing: "Antialiasing";
readonly property string assetMigration: "ATP Asset Migration";
readonly property string assetServer: "Asset Server";
readonly property string atmosphere: "Atmosphere";
readonly property string attachments: "Attachments...";
readonly property string audioNetworkStats: "Audio Network Stats";
readonly property string audioNoiseReduction: "Audio Noise Reduction";
readonly property string audioScope: "Show Scope";
readonly property string audioScopeFiftyFrames: "Fifty";
readonly property string audioScopeFiveFrames: "Five";
readonly property string audioScopeFrames: "Display Frames";
readonly property string audioScopePause: "Pause Scope";
readonly property string audioScopeTwentyFrames: "Twenty";
readonly property string audioStatsShowInjectedStreams: "Audio Stats Show Injected Streams";
readonly property string audioTools: "Show Level Meter";
readonly property string autoMuteAudio: "Auto Mute Microphone";
readonly property string avatarReceiveStats: "Show Receive Stats";
readonly property string back: "Back";
readonly property string bandwidthDetails: "Bandwidth Details";
readonly property string binaryEyelidControl: "Binary Eyelid Control";
readonly property string bookmarkLocation: "Bookmark Location";
readonly property string bookmarks: "Bookmarks";
readonly property string cachesSize: "RAM Caches Size";
readonly property string calibrateCamera: "Calibrate Camera";
readonly property string cameraEntityMode: "Entity Mode";
readonly property string centerPlayerInView: "Center Player In View";
readonly property string chat: "Chat...";
readonly property string collisions: "Collisions";
readonly property string connexion: "Activate 3D Connexion Devices";
readonly property string console_: "Console...";
readonly property string controlWithSpeech: "Control With Speech";
readonly property string copyAddress: "Copy Address to Clipboard";
readonly property string copyPath: "Copy Path to Clipboard";
readonly property string coupleEyelids: "Couple Eyelids";
readonly property string crashInterface: "Crash Interface";
readonly property string debugAmbientOcclusion: "Debug Ambient Occlusion";
readonly property string decreaseAvatarSize: "Decrease Avatar Size";
readonly property string deleteBookmark: "Delete Bookmark...";
readonly property string disableActivityLogger: "Disable Activity Logger";
readonly property string disableEyelidAdjustment: "Disable Eyelid Adjustment";
readonly property string disableLightEntities: "Disable Light Entities";
readonly property string disableNackPackets: "Disable Entity NACK Packets";
readonly property string diskCacheEditor: "Disk Cache Editor";
readonly property string displayCrashOptions: "Display Crash Options";
readonly property string displayHandTargets: "Show Hand Targets";
readonly property string displayModelBounds: "Display Model Bounds";
readonly property string displayModelTriangles: "Display Model Triangles";
readonly property string displayModelElementChildProxies: "Display Model Element Children";
readonly property string displayModelElementProxy: "Display Model Element Bounds";
readonly property string displayDebugTimingDetails: "Display Timing Details";
readonly property string dontDoPrecisionPicking: "Don't Do Precision Picking";
readonly property string dontRenderEntitiesAsScene: "Don't Render Entities as Scene";
readonly property string echoLocalAudio: "Echo Local Audio";
readonly property string echoServerAudio: "Echo Server Audio";
readonly property string enable3DTVMode: "Enable 3DTV Mode";
readonly property string enableCharacterController: "Enable avatar collisions";
readonly property string expandMyAvatarSimulateTiming: "Expand /myAvatar/simulation";
readonly property string expandMyAvatarTiming: "Expand /myAvatar";
readonly property string expandOtherAvatarTiming: "Expand /otherAvatar";
readonly property string expandPaintGLTiming: "Expand /paintGL";
readonly property string expandUpdateTiming: "Expand /update";
readonly property string faceshift: "Faceshift";
readonly property string firstPerson: "First Person";
readonly property string fivePointCalibration: "5 Point Calibration";
readonly property string fixGaze: "Fix Gaze (no saccade)";
readonly property string forward: "Forward";
readonly property string frameTimer: "Show Timer";
readonly property string fullscreenMirror: "Mirror";
readonly property string glowWhenSpeaking: "Glow When Speaking";
readonly property string help: "Help...";
readonly property string increaseAvatarSize: "Increase Avatar Size";
readonly property string independentMode: "Independent Mode";
readonly property string inputMenu: "Avatar>Input Devices";
readonly property string keyboardMotorControl: "Enable Keyboard Motor Control";
readonly property string leapMotionOnHMD: "Leap Motion on HMD";
readonly property string loadScript: "Open and Run Script File...";
readonly property string loadScriptURL: "Open and Run Script from URL...";
readonly property string lodTools: "LOD Tools";
readonly property string login: "Login";
readonly property string log: "Log";
readonly property string logExtraTimings: "Log Extra Timing Details";
readonly property string lowVelocityFilter: "Low Velocity Filter";
readonly property string meshVisible: "Draw Mesh";
readonly property string miniMirror: "Mini Mirror";
readonly property string muteAudio: "Mute Microphone";
readonly property string muteEnvironment: "Mute Environment";
readonly property string muteFaceTracking: "Mute Face Tracking";
readonly property string namesAboveHeads: "Names Above Heads";
readonly property string noFaceTracking: "None";
readonly property string octreeStats: "Entity Statistics";
readonly property string onePointCalibration: "1 Point Calibration";
readonly property string onlyDisplayTopTen: "Only Display Top Ten";
readonly property string outputMenu: "Display";
readonly property string packageModel: "Package Model...";
readonly property string pair: "Pair";
readonly property string physicsShowOwned: "Highlight Simulation Ownership";
readonly property string physicsShowHulls: "Draw Collision Hulls";
readonly property string pipelineWarnings: "Log Render Pipeline Warnings";
readonly property string preferences: "General...";
readonly property string quit: "Quit";
readonly property string reloadAllScripts: "Reload All Scripts";
readonly property string reloadContent: "Reload Content (Clears all caches)";
readonly property string renderBoundingCollisionShapes: "Show Bounding Collision Shapes";
readonly property string renderFocusIndicator: "Show Eye Focus";
readonly property string renderLookAtTargets: "Show Look-at Targets";
readonly property string renderLookAtVectors: "Show Look-at Vectors";
readonly property string renderResolution: "Scale Resolution";
readonly property string renderResolutionOne: "1";
readonly property string renderResolutionTwoThird: "2/3";
readonly property string renderResolutionHalf: "1/2";
readonly property string renderResolutionThird: "1/3";
readonly property string renderResolutionQuarter: "1/4";
readonly property string renderAmbientLight: "Ambient Light";
readonly property string renderAmbientLightGlobal: "Global";
readonly property string renderAmbientLight0: "OLD_TOWN_SQUARE";
readonly property string renderAmbientLight1: "GRACE_CATHEDRAL";
readonly property string renderAmbientLight2: "EUCALYPTUS_GROVE";
readonly property string renderAmbientLight3: "ST_PETERS_BASILICA";
readonly property string renderAmbientLight4: "UFFIZI_GALLERY";
readonly property string renderAmbientLight5: "GALILEOS_TOMB";
readonly property string renderAmbientLight6: "VINE_STREET_KITCHEN";
readonly property string renderAmbientLight7: "BREEZEWAY";
readonly property string renderAmbientLight8: "CAMPUS_SUNSET";
readonly property string renderAmbientLight9: "FUNSTON_BEACH_SUNSET";
readonly property string resetAvatarSize: "Reset Avatar Size";
readonly property string resetSensors: "Reset Sensors";
readonly property string runningScripts: "Running Scripts...";
readonly property string runTimingTests: "Run Timing Tests";
readonly property string scriptEditor: "Script Editor...";
readonly property string scriptedMotorControl: "Enable Scripted Motor Control";
readonly property string showDSConnectTable: "Show Domain Connection Timing";
readonly property string showBordersEntityNodes: "Show Entity Nodes";
readonly property string showRealtimeEntityStats: "Show Realtime Entity Stats";
readonly property string showWhosLookingAtMe: "Show Who's Looking at Me";
readonly property string standingHMDSensorMode: "Standing HMD Sensor Mode";
readonly property string simulateEyeTracking: "Simulate";
readonly property string sMIEyeTracking: "SMI Eye Tracking";
readonly property string stars: "Stars";
readonly property string stats: "Stats";
readonly property string stopAllScripts: "Stop All Scripts";
readonly property string suppressShortTimings: "Suppress Timings Less than 10ms";
readonly property string thirdPerson: "Third Person";
readonly property string threePointCalibration: "3 Point Calibration";
readonly property string throttleFPSIfNotFocus: "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
readonly property string toolWindow: "Tool Window";
readonly property string transmitterDrive: "Transmitter Drive";
readonly property string turnWithHead: "Turn using Head";
readonly property string useAudioForMouth: "Use Audio for Mouth";
readonly property string useCamera: "Use Camera";
readonly property string velocityFilter: "Velocity Filter";
readonly property string visibleToEveryone: "Everyone";
readonly property string visibleToFriends: "Friends";
readonly property string visibleToNoOne: "No one";
readonly property string worldAxes: "World Axes";
}

View file

@ -118,11 +118,89 @@ Window {
}
HifiControls.Table {
tableModel: runningScriptsModel
model: runningScriptsModel
id: table
height: 185
colorScheme: hifi.colorSchemes.dark
anchors.left: parent.left
anchors.right: parent.right
expandSelectedRow: true
itemDelegate: Item {
anchors {
left: parent ? parent.left : undefined
leftMargin: hifi.dimensions.tablePadding
right: parent ? parent.right : undefined
rightMargin: hifi.dimensions.tablePadding
}
FiraSansSemiBold {
id: textItem
text: styleData.value
size: hifi.fontSizes.tableText
color: table.colorScheme == hifi.colorSchemes.light
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
anchors {
left: parent.left
right: parent.right
top: parent.top
topMargin: 3
}
HiFiGlyphs {
id: reloadButton
text: hifi.glyphs.reloadSmall
color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
anchors {
top: parent.top
right: stopButton.left
verticalCenter: parent.verticalCenter
}
MouseArea {
id: reloadButtonArea
anchors { fill: parent; margins: -2 }
onClicked: reloadScript(model.url)
}
}
HiFiGlyphs {
id: stopButton
text: hifi.glyphs.closeSmall
color: stopButtonArea.pressed ? hifi.colors.white : parent.color
anchors {
top: parent.top
right: parent.right
verticalCenter: parent.verticalCenter
}
MouseArea {
id: stopButtonArea
anchors { fill: parent; margins: -2 }
onClicked: stopScript(model.url)
}
}
}
FiraSansSemiBold {
text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : ""
elide: Text.ElideMiddle
size: hifi.fontSizes.tableText
color: table.colorScheme == hifi.colorSchemes.light
? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray)
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
anchors {
top: textItem.bottom
left: parent.left
right: parent.right
}
visible: styleData.selected
}
}
TableViewColumn {
role: "name"
}
}
HifiControls.VerticalSpacer {

View file

@ -0,0 +1,23 @@
//
// FiraSansRegular.qml
//
// Created by David Rowe on 12 May 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Text {
id: root
FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: firaSansRegular.name
}

View file

@ -89,12 +89,16 @@ Item {
readonly property color transparent: "#00ffffff"
// Control specific colors
readonly property color tableRowLightOdd: white50
readonly property color tableRowLightEven: "#1a575757"
readonly property color tableRowDarkOdd: "#80393939"
readonly property color tableRowDarkEven: "#a6181818"
readonly property color tableScrollHandle: "#707070"
readonly property color tableScrollBackground: "#323232"
readonly property color tableRowLightOdd: "#eaeaea" // Equivalent to white50 over #e3e3e3 background
readonly property color tableRowLightEven: "#c6c6c6" // Equivavlent to "#1a575757" over #e3e3e3 background
readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background
readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background
readonly property color tableBackgroundLight: tableRowLightEven
readonly property color tableBackgroundDark: tableRowDarkEven
readonly property color tableScrollHandleLight: tableRowLightOdd
readonly property color tableScrollHandleDark: "#707070"
readonly property color tableScrollBackgroundLight: tableRowLightEven
readonly property color tableScrollBackgroundDark: "#323232"
readonly property color checkboxLightStart: "#ffffff"
readonly property color checkboxLightFinish: "#afafaf"
readonly property color checkboxDarkStart: "#7d7d7d"
@ -137,10 +141,11 @@ Item {
readonly property real textPadding: 8
readonly property real sliderHandleSize: 18
readonly property real sliderGrooveHeight: 8
readonly property real spinnerSize: 42
readonly property real frameIconSize: 22
readonly property real spinnerSize: 50
readonly property real tablePadding: 12
readonly property real tableRowHeight: largeScreen ? 26 : 23
readonly property real tableHeaderHeight: 40
readonly property real tableHeaderHeight: 29
readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30)
readonly property real modalDialogTitleHeight: 40
readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor
@ -157,6 +162,8 @@ Item {
readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12
readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9
readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24
readonly property real tableHeading: dimensions.largeScreen ? 12 : 10
readonly property real tableHeadingIcon: dimensions.largeScreen ? 40 : 33
readonly property real tableText: dimensions.largeScreen ? 15 : 12
readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9
readonly property real iconButton: dimensions.largeScreen ? 13 : 9

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -1,20 +0,0 @@
#version 120
//
// glow_add.frag
// fragment shader
//
// Created by Andrzej Kapolka on 8/14/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the texture containing the original color
uniform sampler2D originalTexture;
void main(void) {
vec4 color = texture2D(originalTexture, gl_TexCoord[0].st);
gl_FragColor = color * (1.0 + color.a);
}

View file

@ -1,23 +0,0 @@
#version 120
//
// glow_add_separate.frag
// fragment shader
//
// Created by Andrzej Kapolka on 8/14/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the texture containing the original color
uniform sampler2D originalTexture;
// the texture containing the blurred color
uniform sampler2D blurredTexture;
void main(void) {
vec4 blurred = texture2D(blurredTexture, gl_TexCoord[0].st);
gl_FragColor = blurred * blurred.a + texture2D(originalTexture, gl_TexCoord[0].st) * (1.0 + blurred.a * 0.5);
}

View file

@ -60,7 +60,7 @@
#include <FramebufferCache.h>
#include <gpu/Batch.h>
#include <gpu/Context.h>
#include <gpu/GLBackend.h>
#include <gpu/gl/GLBackend.h>
#include <HFActionEvent.h>
#include <HFBackEvent.h>
#include <InfoView.h>
@ -108,7 +108,6 @@
#include "audio/AudioScope.h"
#include "avatar/AvatarManager.h"
#include "CrashHandler.h"
#include "input-plugins/SpacemouseManager.h"
#include "devices/DdeFaceTracker.h"
#include "devices/EyeTracker.h"
#include "devices/Faceshift.h"
@ -175,7 +174,6 @@ static const QString FBX_EXTENSION = ".fbx";
static const QString OBJ_EXTENSION = ".obj";
static const QString AVA_JSON_EXTENSION = ".ava.json";
static const int MSECS_PER_SEC = 1000;
static const int MIRROR_VIEW_TOP_PADDING = 5;
static const int MIRROR_VIEW_LEFT_PADDING = 10;
static const int MIRROR_VIEW_WIDTH = 265;
@ -199,6 +197,7 @@ static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check f
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
static const QString INPUT_DEVICE_MENU_PREFIX = "Device: ";
Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS);
const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensions {
@ -349,19 +348,14 @@ public:
};
#endif
enum CustomEventTypes {
Lambda = QEvent::User + 1,
Paint = Lambda + 1,
};
class LambdaEvent : public QEvent {
std::function<void()> _fun;
public:
LambdaEvent(const std::function<void()> & fun) :
QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) {
QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) {
}
LambdaEvent(std::function<void()> && fun) :
QEvent(static_cast<QEvent::Type>(Lambda)), _fun(fun) {
QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) {
}
void call() const { _fun(); }
};
@ -407,6 +401,7 @@ bool setupEssentials(int& argc, char** argv) {
Setting::init();
// Set dependencies
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
DependencyManager::set<ScriptEngines>();
DependencyManager::set<Preferences>();
DependencyManager::set<recording::Deck>();
@ -635,9 +630,11 @@ 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_SEC;
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND;
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
@ -657,19 +654,19 @@ 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
AccountManager& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
auto dialogsManager = DependencyManager::get<DialogsManager>();
connect(&accountManager, &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setIsAgent(true);
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
accountManager->setIsAgent(true);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
@ -766,6 +763,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// Tell our entity edit sender about our known jurisdictions
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
_entityEditSender.setMyAvatar(getMyAvatar());
// For now we're going to set the PPS for outbound packets to be super high, this is
// probably not the right long term solution. But for now, we're going to do this to
@ -1001,7 +999,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get());
if (webEntity) {
webEntity->setProxyWindow(_window->windowHandle());
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->pluginFocusOutEvent();
}
_keyboardFocusedItem = entityItemID;
@ -1054,26 +1052,68 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender();
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
entityPacketSender->setMyAvatar(getMyAvatar());
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
_idleTimer = new QTimer(this);
connect(_idleTimer, &QTimer::timeout, [=] {
idle(usecTimestampNow());
});
connect(this, &Application::beforeAboutToQuit, [=] {
disconnect(_idleTimer);
});
// Setting the interval to zero forces this to get called whenever there are no messages
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
// of logic to abort early if it's being called too often.
_idleTimer->start(0);
// After all of the constructor is completed, then set firstRun to false.
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
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;
QMetaObject::invokeMethod(this, "getUserAgent", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userAgent));
return userAgent;
}
QString userAgent = "Mozilla/5.0 (HighFidelityInterface/" + BuildInfo::VERSION + "; "
+ QSysInfo::productType() + " " + QSysInfo::productVersion() + ")";
auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); };
// For each plugin, add to userAgent
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
for (auto& dp : displayPlugins) {
if (dp->isActive() && dp->isHmd()) {
userAgent += " " + formatPluginName(dp->getName());
}
}
auto inputPlugins= PluginManager::getInstance()->getInputPlugins();
for (auto& ip : inputPlugins) {
if (ip->isActive()) {
userAgent += " " + formatPluginName(ip->getName());
}
}
return userAgent;
}
void Application::checkChangeCursor() {
QMutexLocker locker(&_changeCursorLock);
@ -1105,7 +1145,7 @@ void Application::aboutToQuit() {
emit beforeAboutToQuit();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
QString name = inputPlugin->getName();
QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName();
QAction* action = Menu::getInstance()->getActionForOption(name);
if (action->isChecked()) {
inputPlugin->deactivate();
@ -1262,8 +1302,7 @@ void Application::initializeGL() {
_isGLInitialized = true;
}
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
gpu::Context::init<gpu::GLBackend>();
gpu::Context::init<gpu::gl::GLBackend>();
_gpuContext = std::make_shared<gpu::Context>();
// The gpu context can make child contexts for transfers, so
// we need to restore primary rendering context
@ -1425,8 +1464,7 @@ void Application::initializeUi() {
// This will set up the input plugins UI
_activeInputPlugins.clear();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
QString name = inputPlugin->getName();
if (name == KeyboardMouseDevice::NAME) {
if (KeyboardMouseDevice::NAME == inputPlugin->getName()) {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
}
@ -1441,23 +1479,15 @@ void Application::initializeUi() {
});
}
void Application::paintGL() {
updateHeartbeat();
// Some plugins process message events, potentially leading to
// re-entering a paint event. don't allow further processing if this
// happens
if (_inPaint) {
// Some plugins process message events, allowing paintGL to be called reentrantly.
if (_inPaint || _aboutToQuit) {
return;
}
_inPaint = true;
Finally clearFlagLambda([this] { _inPaint = false; });
// paintGL uses a queued connection, so we can get messages from the queue even after we've quit
// and the plugins have shutdown
if (_aboutToQuit) {
return;
}
_inPaint = true;
Finally clearFlag([this] { _inPaint = false; });
_frameCount++;
_frameCounter.increment();
@ -1503,17 +1533,17 @@ void Application::paintGL() {
renderArgs._context->syncCache();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) {
auto inputs = AvatarInputs::getInstance();
if (inputs->mirrorVisible()) {
PerformanceTimer perfTimer("Mirror");
auto primaryFbo = DependencyManager::get<FramebufferCache>()->getPrimaryFramebuffer();
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
renderArgs._blitFramebuffer = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
auto inputs = AvatarInputs::getInstance();
_mirrorViewRect.moveTo(inputs->x(), inputs->y());
renderRearViewMirror(&renderArgs, _mirrorViewRect);
renderRearViewMirror(&renderArgs, _mirrorViewRect, inputs->mirrorZoomed());
renderArgs._blitFramebuffer.reset();
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
@ -1815,13 +1845,45 @@ bool Application::event(QEvent* event) {
return false;
}
if ((int)event->type() == (int)Lambda) {
static_cast<LambdaEvent*>(event)->call();
// Presentation/painting logic
// TODO: Decouple presentation and painting loops
static bool isPaintingThrottled = false;
if ((int)event->type() == (int)Present) {
if (isPaintingThrottled) {
// If painting (triggered by presentation) is hogging the main thread,
// repost as low priority to avoid hanging the GUI.
// This has the effect of allowing presentation to exceed the paint budget by X times and
// only dropping every (1/X) frames, instead of every ceil(X) frames
// (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS).
removePostedEvents(this, Present);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Present)), Qt::LowEventPriority);
isPaintingThrottled = false;
return true;
}
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
if (shouldPaint(nsecsElapsed)) {
_lastTimeUpdated.start();
idle(nsecsElapsed);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
isPaintingThrottled = true;
return true;
} else if ((int)event->type() == (int)Paint) {
// NOTE: This must be updated as close to painting as possible,
// or AvatarInputs will mysteriously move to the bottom-right
AvatarInputs::getInstance()->update();
paintGL();
isPaintingThrottled = false;
return true;
}
if ((int)event->type() == (int)Paint) {
paintGL();
if ((int)event->type() == (int)Lambda) {
static_cast<LambdaEvent*>(event)->call();
return true;
}
@ -1949,7 +2011,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
if (hasFocus()) {
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->keyPressEvent(event);
}
@ -2283,7 +2345,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
return;
}
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->keyReleaseEvent(event);
}
@ -2315,7 +2377,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
void Application::focusOutEvent(QFocusEvent* event) {
auto inputPlugins = PluginManager::getInstance()->getInputPlugins();
foreach(auto inputPlugin, inputPlugins) {
QString name = inputPlugin->getName();
QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName();
QAction* action = Menu::getInstance()->getActionForOption(name);
if (action && action->isChecked()) {
inputPlugin->pluginFocusOutEvent();
@ -2402,7 +2464,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
return;
}
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->mouseMoveEvent(event);
}
@ -2439,7 +2501,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
if (hasFocus()) {
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->mousePressEvent(event);
}
@ -2484,7 +2546,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
}
if (hasFocus()) {
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->mouseReleaseEvent(event);
}
@ -2511,7 +2573,7 @@ void Application::touchUpdateEvent(QTouchEvent* event) {
return;
}
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->touchUpdateEvent(event);
}
}
@ -2529,7 +2591,7 @@ void Application::touchBeginEvent(QTouchEvent* event) {
return;
}
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->touchBeginEvent(event);
}
@ -2546,7 +2608,7 @@ void Application::touchEndEvent(QTouchEvent* event) {
return;
}
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->touchEndEvent(event);
}
@ -2562,7 +2624,7 @@ void Application::wheelEvent(QWheelEvent* event) const {
return;
}
if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) {
if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) {
_keyboardMouseDevice->wheelEvent(event);
}
}
@ -2599,72 +2661,61 @@ bool Application::acceptSnapshot(const QString& urlString) {
static uint32_t _renderedFrameIndex { INVALID_FRAME };
void Application::idle(uint64_t now) {
// NOTICE NOTICE NOTICE NOTICE
// Do not insert new code between here and the PROFILE_RANGE declaration
// unless you know exactly what you're doing. This idle function can be
// called thousands per second or more, so any additional work that's done
// here will have a serious impact on CPU usage. Only add code after all
// the thottling logic, i.e. after PROFILE_RANGE
// NOTICE NOTICE NOTICE NOTICE
updateHeartbeat();
if (_aboutToQuit || _inPaint) {
return; // bail early, nothing to do here.
bool Application::shouldPaint(float nsecsElapsed) {
if (_aboutToQuit) {
return false;
}
auto displayPlugin = getActiveDisplayPlugin();
// depending on whether we're throttling or not.
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
// perpetuity and not expect events to get backed up.
bool isThrottled = displayPlugin->isThrottled();
// Only run simulation code if more than the targetFramePeriod have passed since last time we ran
// This attempts to lock the simulation at 60 updates per second, regardless of framerate
float timeSinceLastUpdateUs = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC;
float secondsSinceLastUpdate = timeSinceLastUpdateUs / USECS_PER_SECOND;
if (isThrottled && (timeSinceLastUpdateUs / USECS_PER_MSEC) < THROTTLED_SIM_FRAME_PERIOD_MS) {
// Throttling both rendering and idle
return; // bail early, we're throttled and not enough time has elapsed
#ifdef DEBUG_PAINT_DELAY
static uint64_t paintDelaySamples{ 0 };
static uint64_t paintDelayUsecs{ 0 };
paintDelayUsecs += displayPlugin->getPaintDelayUsecs();
static const int PAINT_DELAY_THROTTLE = 1000;
if (++paintDelaySamples % PAINT_DELAY_THROTTLE == 0) {
qCDebug(interfaceapp).nospace() <<
"Paint delay (" << paintDelaySamples << " samples): " <<
(float)paintDelaySamples / paintDelayUsecs << "us";
}
#endif
float msecondsSinceLastUpdate = nsecsElapsed / NSECS_PER_USEC / USECS_PER_MSEC;
// Throttle if requested
if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) {
return false;
}
auto presentCount = displayPlugin->presentCount();
if (presentCount < _renderedFrameIndex) {
_renderedFrameIndex = INVALID_FRAME;
}
// Sync up the _renderedFrameIndex
_renderedFrameIndex = displayPlugin->presentCount();
// Don't saturate the main thread with rendering and simulation,
// unless display plugin has increased by at least one frame
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
// Record what present frame we're on
_renderedFrameIndex = presentCount;
return true;
}
// request a paint, get to it as soon as possible: high priority
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
} else {
// there's no use in simulating or rendering faster then the present rate.
return;
}
void Application::idle(float nsecsElapsed) {
// NOTICE NOTICE NOTICE NOTICE
// do NOT add new code above this line unless you want it to be executed potentially
// thousands of times per second
// NOTICE NOTICE NOTICE NOTICE
// Update the deadlock watchdog
updateHeartbeat();
PROFILE_RANGE(__FUNCTION__);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// These tasks need to be done on our first idle, because we don't want the showing of
// overlay subwindows to do a showDesktop() until after the first time through
static bool firstIdle = true;
if (firstIdle) {
firstIdle = false;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
}
PROFILE_RANGE(__FUNCTION__);
float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND;
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
_keyboardMouseDevice->pluginFocusOutEvent();
_keyboardDeviceHasFocus = false;
@ -2672,15 +2723,9 @@ void Application::idle(uint64_t now) {
_keyboardDeviceHasFocus = true;
}
// We're going to execute idle processing, so restart the last idle timer
_lastTimeUpdated.start();
checkChangeCursor();
Stats::getInstance()->updateStats();
AvatarInputs::getInstance()->update();
_simCounter.increment();
@ -2712,7 +2757,7 @@ void Application::idle(uint64_t now) {
getActiveDisplayPlugin()->idle();
auto inputPlugins = PluginManager::getInstance()->getInputPlugins();
foreach(auto inputPlugin, inputPlugins) {
QString name = inputPlugin->getName();
QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName();
QAction* action = Menu::getInstance()->getActionForOption(name);
if (action && action->isChecked()) {
inputPlugin->idle();
@ -2904,7 +2949,7 @@ void Application::loadSettings() {
}
void Application::saveSettings() const {
sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC);
sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SECOND);
DependencyManager::get<AudioClient>()->saveSettings();
DependencyManager::get<LODManager>()->saveSettings();
@ -3355,22 +3400,18 @@ void Application::update(float deltaTime) {
};
InputPluginPointer keyboardMousePlugin;
bool jointsCaptured = false;
for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
if (inputPlugin->getName() == KeyboardMouseDevice::NAME) {
keyboardMousePlugin = inputPlugin;
} else if (inputPlugin->isActive()) {
inputPlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured);
if (inputPlugin->isJointController()) {
jointsCaptured = true;
}
inputPlugin->pluginUpdate(deltaTime, calibrationData);
}
}
userInputMapper->update(deltaTime);
if (keyboardMousePlugin && keyboardMousePlugin->isActive()) {
keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured);
keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData);
}
_controllerScriptingInterface->updateInputControllers();
@ -3576,10 +3617,6 @@ void Application::update(float deltaTime) {
int Application::sendNackPackets() {
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
return 0;
}
// iterates through all nodes in NodeList
auto nodeList = DependencyManager::get<NodeList>();
@ -3674,11 +3711,11 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
} else {
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
auto rootCode = map.getRootOctalCode();
if (rootCode) {
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
voxelDetailsForCode(rootCode.get(), rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE,
rootDetails.y * TREE_SCALE,
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
@ -3738,11 +3775,11 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
} else {
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
auto rootCode = map.getRootOctalCode();
if (rootCode) {
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
voxelDetailsForCode(rootCode.get(), rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE,
rootDetails.y * TREE_SCALE,
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
@ -4087,7 +4124,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
activeRenderingThread = nullptr;
}
void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region) {
void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed) {
auto originalViewport = renderArgs->_viewport;
// Grab current viewport to reset it at the end
@ -4097,7 +4134,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi
auto myAvatar = getMyAvatar();
// bool eyeRelativeCamera = false;
if (!AvatarInputs::getInstance()->mirrorZoomed()) {
if (!isZoomed) {
_mirrorCamera.setPosition(myAvatar->getChestPosition() +
myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * myAvatar->getScale());
@ -4154,7 +4191,7 @@ void Application::updateWindowTitle() const {
auto nodeList = DependencyManager::get<NodeList>();
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) ";
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();
QString currentPlaceName = DependencyManager::get<AddressManager>()->getHost();
if (currentPlaceName.isEmpty()) {
@ -4181,6 +4218,7 @@ void Application::clearDomainOctreeDetails() {
qCDebug(interfaceapp) << "Clearing domain octree details...";
resetPhysicsReadyInformation();
getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities
// reset our node to stats and node to jurisdiction maps... since these must be changing...
_entityServerJurisdictions.withWriteLock([&] {
@ -4269,9 +4307,9 @@ void Application::nodeKilled(SharedNodePointer node) {
return;
}
unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
voxelDetailsForCode(rootCode.get(), rootDetails);
qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]",
(double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s);
@ -4390,27 +4428,33 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
serverType = "Entity";
}
bool found = false;
jurisdiction->withReadLock([&] {
if (jurisdiction->find(nodeUUID) != jurisdiction->end()) {
found = true;
return;
}
VoxelPositionSize rootDetails;
voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails);
voxelDetailsForCode(octreeStats.getJurisdictionRoot().get(), rootDetails);
qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]",
qPrintable(serverType),
(double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s);
});
// store jurisdiction details for later use
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes());
jurisdiction->withWriteLock([&] {
(*jurisdiction)[nodeUUID] = jurisdictionMap;
});
if (!found) {
// store jurisdiction details for later use
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes());
jurisdiction->withWriteLock([&] {
(*jurisdiction)[nodeUUID] = jurisdictionMap;
});
}
});
return statsMessageLength;
@ -4548,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()) {
@ -4794,8 +4849,8 @@ void Application::takeSnapshot() {
QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot());
AccountManager& accountManager = AccountManager::getInstance();
if (!accountManager.isLoggedIn()) {
auto accountManager = DependencyManager::get<AccountManager>();
if (!accountManager->isLoggedIn()) {
return;
}
@ -5126,21 +5181,23 @@ void Application::updateDisplayMode() {
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
}
static void addInputPluginToMenu(InputPluginPointer inputPlugin, bool active = false) {
static void addInputPluginToMenu(InputPluginPointer inputPlugin) {
auto menu = Menu::getInstance();
QString name = inputPlugin->getName();
QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName();
Q_ASSERT(!menu->menuItemExists(MenuOption::InputMenu, name));
static QActionGroup* inputPluginGroup = nullptr;
if (!inputPluginGroup) {
inputPluginGroup = new QActionGroup(menu);
inputPluginGroup->setExclusive(false);
}
auto parent = menu->getMenu(MenuOption::InputMenu);
auto action = menu->addCheckableActionToQMenuAndActionHash(parent,
name, 0, active, qApp,
name, 0, true, qApp,
SLOT(updateInputModes()));
inputPluginGroup->addAction(action);
inputPluginGroup->setExclusive(false);
Q_ASSERT(menu->menuItemExists(MenuOption::InputMenu, name));
}
@ -5150,10 +5207,8 @@ void Application::updateInputModes() {
auto inputPlugins = PluginManager::getInstance()->getInputPlugins();
static std::once_flag once;
std::call_once(once, [&] {
bool first = true;
foreach(auto inputPlugin, inputPlugins) {
addInputPluginToMenu(inputPlugin, first);
first = false;
addInputPluginToMenu(inputPlugin);
}
});
auto offscreenUi = DependencyManager::get<OffscreenUi>();
@ -5161,7 +5216,7 @@ void Application::updateInputModes() {
InputPluginList newInputPlugins;
InputPluginList removedInputPlugins;
foreach(auto inputPlugin, inputPlugins) {
QString name = inputPlugin->getName();
QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName();
QAction* action = menu->getActionForOption(name);
auto it = std::find(std::begin(_activeInputPlugins), std::end(_activeInputPlugins), inputPlugin);

View file

@ -33,6 +33,7 @@
#include <PhysicalEntitySimulation.h>
#include <PhysicsEngine.h>
#include <plugins/Forward.h>
#include <plugins/DisplayPlugin.h>
#include <ScriptEngine.h>
#include <ShapeManager.h>
#include <SimpleMovingAverage.h>
@ -93,6 +94,12 @@ class Application : public QApplication, public AbstractViewStateInterface, publ
friend class PluginContainerProxy;
public:
enum Event {
Present = DisplayPlugin::Present,
Paint = Present + 1,
Lambda = Paint + 1
};
// FIXME? Empty methods, do we still need them?
static void initPlugins();
static void shutdownPlugins();
@ -105,6 +112,9 @@ public:
QString getPreviousScriptLocation();
void setPreviousScriptLocation(const QString& previousScriptLocation);
// Return an HTTP User-Agent string with OS and device information.
Q_INVOKABLE QString getUserAgent();
void initializeGL();
void initializeUi();
void paintGL();
@ -251,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();
@ -281,7 +295,6 @@ public slots:
private slots:
void showDesktop();
void clearDomainOctreeDetails();
void idle(uint64_t now);
void aboutToQuit();
void resettingDomain();
@ -305,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;
@ -313,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();
@ -320,6 +336,8 @@ private:
void cleanupBeforeQuit();
bool shouldPaint(float nsecsElapsed);
void idle(float nsecsElapsed);
void update(float deltaTime);
// Various helper functions called during update()
@ -332,7 +350,7 @@ private:
glm::vec3 getSunDirection() const;
void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region);
void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed);
int sendNackPackets();
@ -497,7 +515,6 @@ private:
int _avatarAttachmentRequest = 0;
bool _settingsLoaded { false };
QTimer* _idleTimer { nullptr };
bool _fakedMouseEvent { false };

View file

@ -29,6 +29,10 @@ enum CameraMode
};
Q_DECLARE_METATYPE(CameraMode);
#if defined(__GNUC__) && !defined(__clang__)
__attribute__((unused))
#endif
static int cameraModeId = qRegisterMetaType<CameraMode>();
class Camera : public QObject {

View file

@ -35,9 +35,9 @@ const QString API_USER_HEARTBEAT_PATH = "/api/v1/user/heartbeat";
const QString SESSION_ID_KEY = "session_id";
void DiscoverabilityManager::updateLocation() {
AccountManager& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
if (_mode.get() != Discoverability::None && accountManager.isLoggedIn()) {
if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) {
auto addressManager = DependencyManager::get<AddressManager>();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
@ -98,7 +98,7 @@ void DiscoverabilityManager::updateLocation() {
apiPath = API_USER_LOCATION_PATH;
}
accountManager.sendRequest(apiPath, AccountManagerAuth::Required,
accountManager->sendRequest(apiPath, AccountManagerAuth::Required,
QNetworkAccessManager::PutOperation,
callbackParameters, QJsonDocument(rootObject).toJson());
@ -116,7 +116,7 @@ void DiscoverabilityManager::updateLocation() {
heartbeatObject[SESSION_ID_KEY] = QJsonValue();
}
accountManager.sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional,
accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional,
QNetworkAccessManager::PutOperation, callbackParameters,
QJsonDocument(heartbeatObject).toJson());
}
@ -127,12 +127,16 @@ 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);
}
}
void DiscoverabilityManager::removeLocation() {
AccountManager& accountManager = AccountManager::getInstance();
accountManager.sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation);
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation);
}
void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) {

View file

@ -34,7 +34,6 @@
#include "avatar/AvatarManager.h"
#include "devices/DdeFaceTracker.h"
#include "devices/Faceshift.h"
#include "input-plugins/SpacemouseManager.h"
#include "MainWindow.h"
#include "render/DrawStatus.h"
#include "scripting/MenuScriptingInterface.h"
@ -54,7 +53,7 @@ Menu* Menu::getInstance() {
Menu::Menu() {
auto dialogsManager = DependencyManager::get<DialogsManager>();
AccountManager& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
// File/Application menu ----------------------------------
MenuWrapper* fileMenu = addMenu("File");
@ -64,9 +63,9 @@ Menu::Menu() {
addActionToQMenuAndActionHash(fileMenu, MenuOption::Login);
// connect to the appropriate signal of the AccountManager so that we can change the Login/Logout menu item
connect(&accountManager, &AccountManager::profileChanged,
connect(accountManager.data(), &AccountManager::profileChanged,
dialogsManager.data(), &DialogsManager::toggleLoginDialog);
connect(&accountManager, &AccountManager::logoutComplete,
connect(accountManager.data(), &AccountManager::logoutComplete,
dialogsManager.data(), &DialogsManager::toggleLoginDialog);
}
@ -327,12 +326,6 @@ Menu::Menu() {
connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool)));
#endif
// Settings > Input Devices
MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced");
QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu);
inputModeGroup->setExclusive(false);
// Developer menu ----------------------------------
MenuWrapper* developerMenu = addMenu("Developer", "Developer");
@ -410,6 +403,12 @@ Menu::Menu() {
// Developer > Avatar >>>
MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar");
// Settings > Input Devices
MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced");
QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu);
inputModeGroup->setExclusive(false);
// Developer > Avatar > Face Tracking
MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking");
{
@ -500,7 +499,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false,
avatar, SLOT(setEnableDebugDrawSensorToWorldMatrix(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::KeyboardMotorControl,
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl,
Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()),
UNSPECIFIED_POSITION, "Developer");
@ -531,9 +530,6 @@ Menu::Menu() {
// Developer > Network >>>
MenuWrapper* networkMenu = developerMenu->addMenu("Network");
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false,
qApp->getEntityEditPacketSender(),
SLOT(toggleNackPackets()));
addCheckableActionToQMenuAndActionHash(networkMenu,
MenuOption::DisableActivityLogger,
0,
@ -549,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");

View file

@ -84,7 +84,6 @@ namespace MenuOption {
const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable Entity NACK Packets";
const QString DiskCacheEditor = "Disk Cache Editor";
const QString DisplayCrashOptions = "Display Crash Options";
const QString DisplayHandTargets = "Show Hand Targets";
@ -111,12 +110,11 @@ namespace MenuOption {
const QString Forward = "Forward";
const QString FrameTimer = "Show Timer";
const QString FullscreenMirror = "Mirror";
const QString GlowWhenSpeaking = "Glow When Speaking";
const QString Help = "Help...";
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IndependentMode = "Independent Mode";
const QString InputMenu = "Avatar>Input Devices";
const QString KeyboardMotorControl = "Enable Keyboard Motor Control";
const QString InputMenu = "Developer>Avatar>Input Devices";
const QString ActionMotorControl = "Enable Default Motor Control";
const QString LeapMotionOnHMD = "Leap Motion on HMD";
const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL...";
@ -169,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";

View file

@ -31,6 +31,7 @@
#include <SharedUtil.h>
#include <TextRenderer3D.h>
#include <TextureCache.h>
#include <VariantMapToScriptValue.h>
#include <DebugDraw.h>
#include "Application.h"
@ -102,6 +103,18 @@ Avatar::Avatar(RigPointer rig) :
Avatar::~Avatar() {
assert(isDead()); // mark dead before calling the dtor
EntityTreeRenderer* treeRenderer = qApp->getEntities();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
entityTree->deleteEntity(entityID, true, true);
}
});
}
if (_motionState) {
delete _motionState;
_motionState = nullptr;
@ -157,6 +170,90 @@ void Avatar::animateScaleChanges(float deltaTime) {
}
}
void Avatar::updateAvatarEntities() {
// - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity()
// - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited
// - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket
// - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces
// - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData()
// - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true
// - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are...
if (!_avatarEntityDataChanged) {
return;
}
if (getID() == QUuid()) {
return; // wait until MyAvatar gets an ID before doing this.
}
EntityTreeRenderer* treeRenderer = qApp->getEntities();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
bool success = true;
QScriptEngine scriptEngine;
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
// see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties
// and either add or update the entity.
QByteArray jsonByteArray = avatarEntities.value(entityID);
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray);
if (!jsonProperties.isObject()) {
qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex());
continue;
}
QVariant variantProperties = jsonProperties.toVariant();
QVariantMap asMap = variantProperties.toMap();
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties properties;
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties);
properties.setClientOnly(true);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the
// simulationOwner to the owningAvatarID and a high priority.
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) {
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {
success = false;
}
} else {
entity = entityTree->addEntity(entityID, properties);
if (!entity) {
success = false;
}
}
}
AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs();
foreach (auto entityID, recentlyDettachedAvatarEntities) {
if (!_avatarEntityData.contains(entityID)) {
entityTree->deleteEntity(entityID, true, true);
}
}
});
if (success) {
setAvatarEntityDataChanged(false);
}
}
void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
@ -206,6 +303,9 @@ void Avatar::simulate(float deltaTime) {
head->setScale(getUniformScale());
head->simulate(deltaTime, false, !_shouldAnimate);
}
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
}
// update animation for display name fade in/out
@ -229,6 +329,7 @@ void Avatar::simulate(float deltaTime) {
simulateAttachments(deltaTime);
updatePalms();
updateAvatarEntities();
}
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
@ -543,10 +644,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;
}
@ -1088,7 +1185,7 @@ void Avatar::setParentID(const QUuid& parentID) {
if (success) {
setTransform(beforeChangeTransform, success);
if (!success) {
qDebug() << "Avatar::setParentID failed to reset avatar's location.";
qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location.";
}
}
}
@ -1103,7 +1200,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) {
if (success) {
setTransform(beforeChangeTransform, success);
if (!success) {
qDebug() << "Avatar::setParentJointIndex failed to reset avatar's location.";
qCDebug(interfaceapp) << "Avatar::setParentJointIndex failed to reset avatar's location.";
}
}
}

View file

@ -64,6 +64,7 @@ public:
typedef std::shared_ptr<render::Item::PayloadInterface> PayloadPointer;
void init();
void updateAvatarEntities();
void simulate(float deltaTime);
virtual void simulateAttachments(float deltaTime);
@ -235,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 };

View file

@ -59,9 +59,10 @@ using namespace std;
const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);
const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f;
const float MAX_WALKING_SPEED = 2.5f; // human walking speed
const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // keyboard motor gets additive boost below this speed
const float MIN_AVATAR_SPEED = 0.05f; // speed is set to zero below this
const float MAX_WALKING_SPEED = 2.6f; // human walking speed
const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // action motor gets additive boost below this speed
const float MIN_AVATAR_SPEED = 0.05f;
const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this
const float YAW_SPEED_DEFAULT = 120.0f; // degrees/sec
const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec
@ -69,8 +70,7 @@ const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
// to properly follow avatar size.
float MAX_AVATAR_SPEED = 30.0f;
float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED;
float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f;
float MAX_ACTION_MOTOR_SPEED = MAX_AVATAR_SPEED;
float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f;
float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
const int SCRIPTED_MOTOR_CAMERA_FRAME = 0;
@ -86,13 +86,13 @@ MyAvatar::MyAvatar(RigPointer rig) :
Avatar(rig),
_wasPushing(false),
_isPushing(false),
_isBeingPushed(false),
_isBraking(false),
_boomLength(ZOOM_DEFAULT),
_yawSpeed(YAW_SPEED_DEFAULT),
_pitchSpeed(PITCH_SPEED_DEFAULT),
_thrust(0.0f),
_keyboardMotorVelocity(0.0f),
_keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE),
_actionMotorVelocity(0.0f),
_scriptedMotorVelocity(0.0f),
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
@ -234,7 +234,7 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
return AvatarData::toByteArray(cullSmallChanges, sendAll);
}
void MyAvatar::reset(bool andRecenter) {
void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter));
@ -244,9 +244,12 @@ void MyAvatar::reset(bool andRecenter) {
// Reset dynamic state.
_wasPushing = _isPushing = _isBraking = false;
_follow.deactivate();
_skeletonModel->reset();
getHead()->reset();
_targetVelocity = glm::vec3(0.0f);
if (andReload) {
_skeletonModel->reset();
}
if (andHead) { // which drives camera in desktop
getHead()->reset();
}
setThrust(glm::vec3(0.0f));
if (andRecenter) {
@ -262,8 +265,9 @@ void MyAvatar::reset(bool andRecenter) {
setPosition(worldBodyPos);
setOrientation(worldBodyRot);
// now sample the new hmd orientation AFTER sensor reset.
updateFromHMDSensorMatrix(qApp->getHMDSensorPose());
// now sample the new hmd orientation AFTER sensor reset, which should be identity.
glm::mat4 identity;
updateFromHMDSensorMatrix(identity);
// update the body in sensor space using the new hmd sensor sample
_bodySensorMatrix = deriveBodyFromHMDSensor();
@ -310,6 +314,10 @@ void MyAvatar::update(float deltaTime) {
head->setAudioLoudness(audio->getLastInputLoudness());
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
if (_avatarEntityDataLocallyEdited) {
sendIdentityPacket();
}
simulate(deltaTime);
currentEnergy += energyChargeRate;
@ -425,7 +433,14 @@ void MyAvatar::simulate(float deltaTime) {
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
if (entityTree) {
bool flyingAllowed = true;
bool ghostingAllowed = true;
entityTree->withWriteLock([&] {
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
if (zone) {
flyingAllowed = zone->getFlyingAllowed();
ghostingAllowed = zone->getGhostingAllowed();
}
auto now = usecTimestampNow();
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
MovingEntitiesOperator moveOperator(entityTree);
@ -442,7 +457,8 @@ void MyAvatar::simulate(float deltaTime) {
EntityItemProperties properties = entity->getProperties();
properties.setQueryAACubeDirty();
properties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
entity->setLastBroadcast(usecTimestampNow());
}
}
@ -453,7 +469,13 @@ void MyAvatar::simulate(float deltaTime) {
entityTree->recurseTreeWithOperator(&moveOperator);
}
});
_characterController.setFlyingAllowed(flyingAllowed);
if (!_characterController.isEnabled() && !ghostingAllowed) {
_characterController.setEnabled(true);
}
}
updateAvatarEntities();
}
// thread-safe
@ -670,8 +692,6 @@ void MyAvatar::saveData() {
settings.setValue("headPitch", getHead()->getBasePitch());
settings.setValue("pupilDilation", getHead()->getPupilDilation());
settings.setValue("leanScale", _leanScale);
settings.setValue("scale", _targetScale);
@ -701,9 +721,20 @@ void MyAvatar::saveData() {
}
settings.endArray();
settings.beginWriteArray("avatarEntityData");
int avatarEntityIndex = 0;
for (auto entityID : _avatarEntityData.keys()) {
settings.setArrayIndex(avatarEntityIndex);
settings.setValue("id", entityID);
settings.setValue("properties", _avatarEntityData.value(entityID));
avatarEntityIndex++;
}
settings.endArray();
settings.setValue("displayName", _displayName);
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving);
settings.endGroup();
}
@ -778,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));
@ -812,9 +841,21 @@ void MyAvatar::loadData() {
settings.endArray();
setAttachmentData(attachmentData);
int avatarEntityCount = settings.beginReadArray("avatarEntityData");
for (int i = 0; i < avatarEntityCount; i++) {
settings.setArrayIndex(i);
QUuid entityID = settings.value("id").toUuid();
// QUuid entityID = QUuid::createUuid(); // generate a new ID
QByteArray properties = settings.value("properties").toByteArray();
updateAvatarEntity(entityID, properties);
}
settings.endArray();
setAvatarEntityDataChanged(true);
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool());
settings.endGroup();
@ -1166,8 +1207,46 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const {
return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::updateMotors() {
_characterController.clearMotors();
glm::quat motorRotation;
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
if (_characterController.getState() == CharacterController::State::Hover) {
motorRotation = getHead()->getCameraOrientation();
} else {
motorRotation = getOrientation();
}
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
if (_isPushing || _isBraking || !_isBeingPushed) {
_characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
} else {
// _isBeingPushed must be true --> disable action motor by giving it a long timescale,
// otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts
_characterController.addMotor(_actionMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE);
}
}
if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
motorRotation = getHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else {
// world-frame
motorRotation = glm::quat();
}
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale);
}
// legacy support for 'MyAvatar::applyThrust()', which has always been implemented as a
// short-lived linearAcceleration
_characterController.setLinearAcceleration(_thrust);
_thrust = Vectors::ZERO;
}
void MyAvatar::prepareForPhysicsSimulation() {
relayDriveKeysToCharacterController();
updateMotors();
bool success;
glm::vec3 parentVelocity = getParentVelocity(success);
@ -1177,7 +1256,6 @@ void MyAvatar::prepareForPhysicsSimulation() {
}
_characterController.setParentVelocity(parentVelocity);
_characterController.setTargetVelocity(getTargetVelocity());
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
if (qApp->isHMDMode()) {
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
@ -1190,11 +1268,17 @@ void MyAvatar::prepareForPhysicsSimulation() {
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
glm::vec3 position = getPosition();
glm::quat orientation = getOrientation();
_characterController.getPositionAndOrientation(position, orientation);
if (_characterController.isEnabled()) {
_characterController.getPositionAndOrientation(position, orientation);
}
nextAttitude(position, orientation);
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
if (_characterController.isEnabled()) {
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
} else {
setVelocity(getVelocity() + _characterController.getFollowVelocity());
}
}
QString MyAvatar::getScriptedMotorFrame() const {
@ -1234,7 +1318,7 @@ void MyAvatar::setScriptedMotorFrame(QString frame) {
}
void MyAvatar::clearScriptableSettings() {
_scriptedMotorVelocity = glm::vec3(0.0f);
_scriptedMotorVelocity = Vectors::ZERO;
_scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE;
}
@ -1499,163 +1583,97 @@ void MyAvatar::updateOrientation(float deltaTime) {
}
}
glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool isHovering) {
if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) {
return localVelocity;
}
// compute motor efficiency
// The timescale of the motor is the approximate time it takes for the motor to
// accomplish its intended localVelocity. A short timescale makes the motor strong,
// and a long timescale makes it weak. The value of timescale to use depends
// on what the motor is doing:
//
// (1) braking --> short timescale (aggressive motor assertion)
// (2) pushing --> medium timescale (mild motor assertion)
// (3) inactive --> long timescale (gentle friction for low speeds)
const float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f;
const float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f;
const float MIN_KEYBOARD_BRAKE_SPEED = 0.3f;
float timescale = MAX_KEYBOARD_MOTOR_TIMESCALE;
bool isThrust = (glm::length2(_thrust) > EPSILON);
if (_isPushing || isThrust ||
(_scriptedMotorTimescale < MAX_KEYBOARD_MOTOR_TIMESCALE &&
(_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED))) {
// we don't want to brake if something is pushing the avatar around
timescale = _keyboardMotorTimescale;
void MyAvatar::updateActionMotor(float deltaTime) {
bool thrustIsPushing = (glm::length2(_thrust) > EPSILON);
bool scriptedMotorIsPushing = (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)
&& _scriptedMotorTimescale < MAX_CHARACTER_MOTOR_TIMESCALE;
_isBeingPushed = thrustIsPushing || scriptedMotorIsPushing;
if (_isPushing || _isBeingPushed) {
// we don't want the motor to brake if a script is pushing the avatar around
// (we assume the avatar is driving itself via script)
_isBraking = false;
} else {
float speed = glm::length(localVelocity);
_isBraking = _wasPushing || (_isBraking && speed > MIN_KEYBOARD_BRAKE_SPEED);
if (_isBraking) {
timescale = MIN_KEYBOARD_MOTOR_TIMESCALE;
}
float speed = glm::length(_actionMotorVelocity);
const float MIN_ACTION_BRAKE_SPEED = 0.1f;
_isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED);
}
_wasPushing = _isPushing || isThrust;
_isPushing = false;
float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
glm::vec3 newLocalVelocity = localVelocity;
// compute action input
glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT;
// FIXME how do I implement step translation as well?
float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]);
if (keyboardInput) {
// Compute keyboard input
glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT;
glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT;
glm::vec3 direction = front + right;
CharacterController::State state = _characterController.getState();
if (state == CharacterController::State::Hover) {
// we're flying --> support vertical motion
glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP;
direction += up;
}
glm::vec3 direction = front + right + up;
float directionLength = glm::length(direction);
_wasPushing = _isPushing;
float directionLength = glm::length(direction);
_isPushing = directionLength > EPSILON;
//qCDebug(interfaceapp, "direction = (%.5f, %.5f, %.5f)", direction.x, direction.y, direction.z);
// Compute motor magnitude
if (directionLength > EPSILON) {
direction /= directionLength;
if (isHovering) {
// we're flying --> complex acceleration curve with high max speed
float motorSpeed = glm::length(_keyboardMotorVelocity);
float finalMaxMotorSpeed = getUniformScale() * MAX_KEYBOARD_MOTOR_SPEED;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
if (motorSpeed < maxBoostSpeed) {
// an active keyboard motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
motorSpeed += MIN_AVATAR_SPEED * boostCoefficient;
motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient;
} else if (motorSpeed > finalMaxMotorSpeed) {
motorSpeed = finalMaxMotorSpeed;
}
_keyboardMotorVelocity = motorSpeed * direction;
} else {
// we're using a floor --> simple exponential decay toward target walk speed
const float WALK_ACCELERATION_TIMESCALE = 0.7f; // seconds to decrease delta to 1/e
_keyboardMotorVelocity = MAX_WALKING_SPEED * direction;
motorEfficiency = glm::clamp(deltaTime / WALK_ACCELERATION_TIMESCALE, 0.0f, 1.0f);
}
_isPushing = true;
}
newLocalVelocity = localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity);
// normalize direction
if (_isPushing) {
direction /= directionLength;
} else {
_keyboardMotorVelocity = glm::vec3(0.0f);
newLocalVelocity = (1.0f - motorEfficiency) * localVelocity;
if (!isHovering && !_wasPushing) {
float speed = glm::length(newLocalVelocity);
if (speed > MIN_AVATAR_SPEED) {
// add small constant friction to help avatar drift to a stop sooner at low speeds
const float CONSTANT_FRICTION_DECELERATION = MIN_AVATAR_SPEED / 0.20f;
newLocalVelocity *= (speed - timescale * CONSTANT_FRICTION_DECELERATION) / speed;
direction = Vectors::ZERO;
}
if (state == CharacterController::State::Hover) {
// we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed
float motorSpeed = glm::length(_actionMotorVelocity);
float finalMaxMotorSpeed = getUniformScale() * MAX_ACTION_MOTOR_SPEED;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
if (_isPushing) {
if (motorSpeed < maxBoostSpeed) {
// an active action motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
motorSpeed += MIN_AVATAR_SPEED * boostCoefficient;
} else if (motorSpeed > finalMaxMotorSpeed) {
motorSpeed = finalMaxMotorSpeed;
}
}
_actionMotorVelocity = motorSpeed * direction;
} else {
// we're interacting with a floor --> simple horizontal speed and exponential decay
_actionMotorVelocity = MAX_WALKING_SPEED * direction;
}
float boomChange = _driveKeys[ZOOM];
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
return newLocalVelocity;
}
glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) {
// NOTE: localVelocity is in camera-frame because that's the frame of the default avatar motor
if (! (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) {
return localVelocity;
}
glm::vec3 deltaVelocity(0.0f);
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
// camera frame
deltaVelocity = _scriptedMotorVelocity - localVelocity;
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
// avatar frame
glm::quat rotation = glm::inverse(getHead()->getCameraOrientation()) * getOrientation();
deltaVelocity = rotation * _scriptedMotorVelocity - localVelocity;
} else {
// world-frame
glm::quat rotation = glm::inverse(getHead()->getCameraOrientation());
deltaVelocity = rotation * _scriptedMotorVelocity - localVelocity;
}
float motorEfficiency = glm::clamp(deltaTime / _scriptedMotorTimescale, 0.0f, 1.0f);
return localVelocity + motorEfficiency * deltaVelocity;
}
void MyAvatar::updatePosition(float deltaTime) {
// rotate velocity into camera frame
glm::quat rotation = getHead()->getCameraOrientation();
glm::vec3 localVelocity = glm::inverse(rotation) * _targetVelocity;
bool isHovering = _characterController.getState() == CharacterController::State::Hover;
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isHovering);
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
// rotate back into world-frame
_targetVelocity = rotation * newLocalVelocity;
_targetVelocity += _thrust * deltaTime;
_thrust = glm::vec3(0.0f);
// cap avatar speed
float speed = glm::length(_targetVelocity);
if (speed > MAX_AVATAR_SPEED) {
_targetVelocity *= MAX_AVATAR_SPEED / speed;
speed = MAX_AVATAR_SPEED;
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
updateActionMotor(deltaTime);
}
if (speed > MIN_AVATAR_SPEED && !_characterController.isEnabled()) {
// update position ourselves
applyPositionDelta(deltaTime * _targetVelocity);
vec3 velocity = getVelocity();
const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
if (!_characterController.isEnabled()) {
// _characterController is not in physics simulation but it can still compute its target velocity
updateMotors();
_characterController.computeNewVelocity(deltaTime, velocity);
float speed2 = glm::length2(velocity);
if (speed2 > MIN_AVATAR_SPEED_SQUARED) {
// update position ourselves
applyPositionDelta(deltaTime * velocity);
}
measureMotionDerivatives(deltaTime);
} // else physics will move avatar later
// update _moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f;
_moving = speed > MOVING_SPEED_THRESHOLD;
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
} else {
// physics physics simulation updated elsewhere
float speed2 = glm::length2(velocity);
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
}
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) > 0.1f || fabs(_driveKeys[TRANSLATE_X]) > 0.1f)) {
@ -1802,17 +1820,31 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
}
Menu* menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
if (menu->isOptionChecked(MenuOption::ActionMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_ACTION_MOTOR_ENABLED;
} else {
_motionBehaviors &= ~AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
_motionBehaviors &= ~AVATAR_MOTION_ACTION_MOTOR_ENABLED;
}
if (menu->isOptionChecked(MenuOption::ScriptedMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
} else {
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
}
_characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
bool ghostingAllowed = true;
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
if (entityTreeRenderer) {
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
if (zone) {
ghostingAllowed = zone->getGhostingAllowed();
}
}
bool checked = menu->isOptionChecked(MenuOption::EnableCharacterController);
if (!ghostingAllowed) {
checked = true;
}
_characterController.setEnabled(checked);
}
void MyAvatar::clearDriveKeys() {

View file

@ -94,7 +94,7 @@ public:
AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; }
AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; }
Q_INVOKABLE void reset(bool andRecenter = false);
Q_INVOKABLE void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
void update(float deltaTime);
virtual void postUpdate(float deltaTime) override;
void preDisplaySide(RenderArgs* renderArgs);
@ -160,6 +160,8 @@ public:
Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; }
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
// get/set avatar data
void saveData();
@ -218,6 +220,7 @@ public:
MyCharacterController* getCharacterController() { return &_characterController; }
const MyCharacterController* getCharacterController() const { return &_characterController; }
void updateMotors();
void prepareForPhysicsSimulation();
void harvestResultsFromPhysicsSimulation(float deltaTime);
@ -350,6 +353,7 @@ private:
float _driveKeys[MAX_DRIVE_KEYS];
bool _wasPushing;
bool _isPushing;
bool _isBeingPushed;
bool _isBraking;
float _boomLength;
@ -358,9 +362,8 @@ private:
glm::vec3 _thrust; // impulse accumulator for outside sources
glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard)
float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script)
glm::vec3 _actionMotorVelocity; // target local-frame velocity of avatar (default controller actions)
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (analog script)
float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity
int _scriptedMotorFrame;
quint32 _motionBehaviors;
@ -384,8 +387,7 @@ private:
// private methods
void updateOrientation(float deltaTime);
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool isHovering);
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
void updateActionMotor(float deltaTime);
void updatePosition(float deltaTime);
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void initHeadBones();
@ -396,6 +398,7 @@ private:
QString _fullAvatarModelName;
QUrl _animGraphUrl {""};
bool _useSnapTurn { true };
bool _clearOverlayWhenDriving { false };
// cache of the current HMD sensor position and orientation
// in sensor space.

View file

@ -144,11 +144,11 @@ int main(int argc, const char* argv[]) {
// If we failed the OpenGLVersion check, log it.
if (override) {
auto& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {
UserActivityLogger::getInstance().insufficientGLVersion(glData);
} else {
QObject::connect(&AccountManager::getInstance(), &AccountManager::loginComplete, [glData](){
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [glData](){
static bool loggedInsufficientGL = false;
if (!loggedInsufficientGL) {
UserActivityLogger::getInstance().insufficientGLVersion(glData);
@ -168,9 +168,9 @@ int main(int argc, const char* argv[]) {
QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection);
#ifdef HAS_BUGSPLAT
AccountManager& accountManager = AccountManager::getInstance();
crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername()));
QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) {
auto accountManager = DependencyManager::get<AccountManager>();
crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername()));
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) {
crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername));
});

View file

@ -19,14 +19,14 @@ AccountScriptingInterface* AccountScriptingInterface::getInstance() {
}
bool AccountScriptingInterface::isLoggedIn() {
AccountManager& accountManager = AccountManager::getInstance();
return accountManager.isLoggedIn();
auto accountManager = DependencyManager::get<AccountManager>();
return accountManager->isLoggedIn();
}
QString AccountScriptingInterface::getUsername() {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
return accountManager.getAccountInfo().getUsername();
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {
return accountManager->getAccountInfo().getUsername();
} else {
return "Unknown user";
}

View file

@ -196,7 +196,7 @@ bool AssetMappingModel::isKnownFolder(QString path) const {
return false;
}
static int assetMappingModelMetatypeId = qRegisterMetaType<AssetMappingModel*>("AssetMappingModel*");
int assetMappingModelMetatypeId = qRegisterMetaType<AssetMappingModel*>("AssetMappingModel*");
void AssetMappingModel::refresh() {
qDebug() << "Refreshing asset mapping model";

View file

@ -17,10 +17,10 @@
#include "GlobalServicesScriptingInterface.h"
GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
connect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
connect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
connect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected);
_downloading = false;
QTimer* checkDownloadTimer = new QTimer(this);
@ -34,10 +34,10 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
}
GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance();
disconnect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
disconnect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
disconnect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected);
auto accountManager = DependencyManager::get<AccountManager>();
disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
disconnect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected);
}
GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() {
@ -46,7 +46,7 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance(
}
const QString& GlobalServicesScriptingInterface::getUsername() const {
return AccountManager::getInstance().getAccountInfo().getUsername();
return DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();
}
void GlobalServicesScriptingInterface::loggedOut() {

View file

@ -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);

View file

@ -23,7 +23,6 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
auto addressManager = DependencyManager::get<AddressManager>();
connect(addressManager.data(), &AddressManager::lookupResultIsOffline, this, &AddressBarDialog::displayAddressOfflineMessage);
connect(addressManager.data(), &AddressManager::lookupResultIsNotFound, this, &AddressBarDialog::displayAddressNotFoundMessage);
connect(addressManager.data(), &AddressManager::lookupResultsFinished, this, &AddressBarDialog::hide);
connect(addressManager.data(), &AddressManager::goBackPossible, this, [this] (bool isPossible) {
if (isPossible != _backEnabled) {
_backEnabled = isPossible;
@ -40,10 +39,6 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
}
void AddressBarDialog::hide() {
((QQuickItem*)parent())->setEnabled(false);
}
void AddressBarDialog::loadAddress(const QString& address) {
qDebug() << "Called LoadAddress with address " << address;
if (!address.isEmpty()) {

View file

@ -33,7 +33,6 @@ signals:
protected:
void displayAddressOfflineMessage();
void displayAddressNotFoundMessage();
void hide();
Q_INVOKABLE void loadAddress(const QString& address);
Q_INVOKABLE void loadHome();

View file

@ -13,12 +13,12 @@
#include <avatar/AvatarManager.h>
#include <GLMHelpers.h>
#include <gpu/GLBackendShared.h>
#include <FramebufferCache.h>
#include <GLMHelpers.h>
#include <OffscreenUi.h>
#include <CursorManager.h>
#include <PerfStat.h>
#include <gl/Config.h>
#include "AudioClient.h"
#include "audio/AudioScope.h"
@ -55,7 +55,6 @@ ApplicationOverlay::~ApplicationOverlay() {
// Renders the overlays either to a texture or to the screen
void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
PROFILE_RANGE(__FUNCTION__);
CHECK_GL_ERROR();
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()");
buildFramebufferObject();
@ -89,8 +88,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
});
renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch
CHECK_GL_ERROR();
}
void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
@ -295,10 +292,6 @@ gpu::TexturePointer ApplicationOverlay::acquireOverlay() {
return gpu::TexturePointer();
}
auto result = _overlayFramebuffer->getRenderBuffer(0);
auto textureId = gpu::GLBackend::getTextureID(result, false);
if (!textureId) {
qDebug() << "Missing texture";
}
_overlayFramebuffer->setRenderBuffer(0, gpu::TexturePointer());
return result;
}

View file

@ -24,14 +24,15 @@ HIFI_QML_DEF(LoginDialog)
LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent),
_rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString())
{
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted);
connect(&AccountManager::getInstance(), &AccountManager::loginFailed,
connect(accountManager.data(), &AccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed);
}
void LoginDialog::toggleAction() {
AccountManager& accountManager = AccountManager::getInstance();
auto accountManager = DependencyManager::get<AccountManager>();
QAction* loginAction = Menu::getInstance()->getActionForOption(MenuOption::Login);
Q_CHECK_PTR(loginAction);
static QMetaObject::Connection connection;
@ -39,10 +40,10 @@ void LoginDialog::toggleAction() {
disconnect(connection);
}
if (accountManager.isLoggedIn()) {
if (accountManager->isLoggedIn()) {
// change the menu item to logout
loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername());
connection = connect(loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
loginAction->setText("Logout " + accountManager->getAccountInfo().getUsername());
connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout);
} else {
// change the menu item to login
loginAction->setText("Login");
@ -78,7 +79,7 @@ QString LoginDialog::rootUrl() const {
void LoginDialog::login(const QString& username, const QString& password) {
qDebug() << "Attempting to login " << username;
setStatusText("Logging in...");
AccountManager::getInstance().requestAccessToken(username, password);
DependencyManager::get<AccountManager>()->requestAccessToken(username, password);
}
void LoginDialog::openUrl(const QString& url) {

View file

@ -433,13 +433,13 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
}
const JurisdictionMap& map = serverJurisdictions[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
auto rootCode = map.getRootOctalCode();
if (rootCode) {
QString rootCodeHex = octalCodeToHexString(rootCode);
QString rootCodeHex = octalCodeToHexString(rootCode.get());
VoxelPositionSize rootDetails;
voxelDetailsForCode(rootCode, rootDetails);
voxelDetailsForCode(rootCode.get(), rootDetails);
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
serverDetails << " jurisdiction: "
<< qPrintable(rootCodeHex)

View file

@ -67,6 +67,40 @@ void OverlayConductor::update(float dt) {
}
void OverlayConductor::updateMode() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (myAvatar->getClearOverlayWhenDriving()) {
float speed = glm::length(myAvatar->getVelocity());
const float MIN_DRIVING = 0.2f;
const float MAX_NOT_DRIVING = 0.01f;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000;
bool nowDriving = _driving; // Assume current _driving mode unless...
if (speed > MIN_DRIVING) { // ... we're definitely moving...
nowDriving = true;
} else if (speed < MAX_NOT_DRIVING) { // ... or definitely not.
nowDriving = false;
}
// Check that we're in this new mode for long enough to really trigger a transition.
if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer.
_timeInPotentialMode = 0;
} else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now.
_timeInPotentialMode = usecTimestampNow();
} else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) {
_timeInPotentialMode = 0; // a real transition
if (nowDriving) {
_wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
} else { // reset when coming out of driving
_mode = FLAT; // Seems appropriate to let things reset, below, after the following.
// All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments.
qApp->getActiveDisplayPlugin()->resetSensors();
myAvatar->reset(true, false, false);
}
if (_wantsOverlays) {
setEnabled(!nowDriving, false);
}
_driving = nowDriving;
} // Else haven't accumulated enough time in new mode, but keep timing.
}
Mode newMode;
if (qApp->isHMDMode()) {
@ -84,10 +118,9 @@ void OverlayConductor::updateMode() {
qApp->getApplicationCompositor().setModelTransform(identity);
break;
}
case STANDING: {
case STANDING: { // STANDING mode is not currently used.
// enter the STANDING state
// place the overlay at the current hmd position in world space
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
Transform t;
t.setTranslation(extractTranslation(camMat));
@ -103,15 +136,18 @@ void OverlayConductor::updateMode() {
}
_mode = newMode;
}
void OverlayConductor::setEnabled(bool enabled) {
void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) {
if (enabled == _enabled) {
return;
}
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway.
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
}
_enabled = enabled; // set the new value
@ -124,8 +160,10 @@ void OverlayConductor::setEnabled(bool enabled) {
qApp->getOverlays().enable();
// enable QML events
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(true);
if (toggleQmlEvents) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(true);
}
if (_mode == STANDING) {
// place the overlay at the current hmd position in world space
@ -144,8 +182,10 @@ void OverlayConductor::setEnabled(bool enabled) {
qApp->getOverlays().disable();
// disable QML events
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(false);
if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(false);
}
}
}

View file

@ -17,7 +17,7 @@ public:
~OverlayConductor();
void update(float dt);
void setEnabled(bool enable);
void setEnabled(bool enable, bool toggleQmlEvents = true);
bool getEnabled() const;
private:
@ -31,6 +31,9 @@ private:
Mode _mode { FLAT };
bool _enabled { false };
bool _driving { false };
quint64 _timeInPotentialMode { 0 };
bool _wantsOverlays { true };
};
#endif

View file

@ -61,6 +61,11 @@ void setupPreferences() {
auto setter = [=](bool value) { myAvatar->setSnapTurn(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter));
}
{
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); };
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter));
}
{
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); };
@ -144,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); };

View file

@ -92,7 +92,7 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) {
QUrl currentURL = DependencyManager::get<AddressManager>()->currentAddress();
shot.setText(URL, currentURL.toString());
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();
// normalize username, replace all non alphanumeric with '-'
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
@ -144,14 +144,15 @@ const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...<b
QString SnapshotUploader::uploadSnapshot(const QUrl& fileUrl) {
if (AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().isEmpty()) {
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->getAccountInfo().getDiscourseApiKey().isEmpty()) {
OffscreenUi::warning(nullptr, "", "Your Discourse API key is missing, you cannot share snapshots. Please try to relog.");
return QString();
}
QHttpPart apiKeyPart;
apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"api_key\""));
apiKeyPart.setBody(AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().toLatin1());
apiKeyPart.setBody(accountManager->getAccountInfo().getDiscourseApiKey().toLatin1());
QString filename = fileUrl.toLocalFile();
qDebug() << filename;
@ -206,7 +207,7 @@ QString SnapshotUploader::sendForumPost(const QString& snapshotPath, const QStri
QUrl forumUrl(FORUM_POST_URL);
QUrlQuery query;
query.addQueryItem("api_key", AccountManager::getInstance().getAccountInfo().getDiscourseApiKey());
query.addQueryItem("api_key", DependencyManager::get<AccountManager>()->getAccountInfo().getDiscourseApiKey());
query.addQueryItem("topic_id", FORUM_REPLY_TO_TOPIC);
query.addQueryItem("raw", FORUM_POST_TEMPLATE.arg(snapshotPath, notes));
forumUrl.setQuery(query);

View file

@ -20,14 +20,12 @@ Overlay::Overlay() :
_renderItemID(render::Item::INVALID_ITEM_ID),
_isLoaded(true),
_alpha(DEFAULT_ALPHA),
_glowLevel(0.0f),
_pulse(0.0f),
_pulseMax(0.0f),
_pulseMin(0.0f),
_pulsePeriod(1.0f),
_pulseDirection(1.0f),
_lastPulseUpdate(usecTimestampNow()),
_glowLevelPulse(0.0f),
_alphaPulse(0.0f),
_colorPulse(0.0f),
_color(DEFAULT_OVERLAY_COLOR),
@ -40,14 +38,12 @@ Overlay::Overlay(const Overlay* overlay) :
_renderItemID(render::Item::INVALID_ITEM_ID),
_isLoaded(overlay->_isLoaded),
_alpha(overlay->_alpha),
_glowLevel(overlay->_glowLevel),
_pulse(overlay->_pulse),
_pulseMax(overlay->_pulseMax),
_pulseMin(overlay->_pulseMin),
_pulsePeriod(overlay->_pulsePeriod),
_pulseDirection(overlay->_pulseDirection),
_lastPulseUpdate(usecTimestampNow()),
_glowLevelPulse(overlay->_glowLevelPulse),
_alphaPulse(overlay->_alphaPulse),
_colorPulse(overlay->_colorPulse),
_color(overlay->_color),
@ -69,10 +65,6 @@ void Overlay::setProperties(const QVariantMap& properties) {
if (properties["alpha"].isValid()) {
setAlpha(properties["alpha"].toFloat());
}
if (properties["glowLevel"].isValid()) {
setGlowLevel(properties["glowLevel"].toFloat());
}
if (properties["pulseMax"].isValid()) {
setPulseMax(properties["pulseMax"].toFloat());
@ -86,10 +78,6 @@ void Overlay::setProperties(const QVariantMap& properties) {
setPulsePeriod(properties["pulsePeriod"].toFloat());
}
if (properties["glowLevelPulse"].isValid()) {
setGlowLevelPulse(properties["glowLevelPulse"].toFloat());
}
if (properties["alphaPulse"].isValid()) {
setAlphaPulse(properties["alphaPulse"].toFloat());
}
@ -118,9 +106,6 @@ QVariant Overlay::getProperty(const QString& property) {
if (property == "alpha") {
return _alpha;
}
if (property == "glowLevel") {
return _glowLevel;
}
if (property == "pulseMax") {
return _pulseMax;
}
@ -130,9 +115,6 @@ QVariant Overlay::getProperty(const QString& property) {
if (property == "pulsePeriod") {
return _pulsePeriod;
}
if (property == "glowLevelPulse") {
return _glowLevelPulse;
}
if (property == "alphaPulse") {
return _alphaPulse;
}
@ -176,16 +158,8 @@ float Overlay::getAlpha() {
return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel);
}
float Overlay::getGlowLevel() {
if (_glowLevelPulse == 0.0f) {
return _glowLevel;
}
float pulseLevel = updatePulse();
return (_glowLevelPulse >= 0.0f) ? _glowLevel * pulseLevel : _glowLevel * (1.0f - pulseLevel);
}
// glow level travels from min to max, then max to min in one period.
// pulse travels from min to max, then max to min in one period.
float Overlay::updatePulse() {
if (_pulsePeriod <= 0.0f) {
return _pulse;
@ -196,25 +170,25 @@ float Overlay::updatePulse() {
float elapsedPeriods = elapsedSeconds / _pulsePeriod;
// we can safely remove any "full" periods, since those just rotate us back
// to our final glow level
// to our final pulse level
elapsedPeriods = fmod(elapsedPeriods, 1.0f);
_lastPulseUpdate = now;
float glowDistance = (_pulseMax - _pulseMin);
float glowDistancePerPeriod = glowDistance * 2.0f;
float pulseDistance = (_pulseMax - _pulseMin);
float pulseDistancePerPeriod = pulseDistance * 2.0f;
float glowDelta = _pulseDirection * glowDistancePerPeriod * elapsedPeriods;
float newGlow = _pulse + glowDelta;
float pulseDelta = _pulseDirection * pulseDistancePerPeriod * elapsedPeriods;
float newPulse = _pulse + pulseDelta;
float limit = (_pulseDirection > 0.0f) ? _pulseMax : _pulseMin;
float passedLimit = (_pulseDirection > 0.0f) ? (newGlow >= limit) : (newGlow <= limit);
float passedLimit = (_pulseDirection > 0.0f) ? (newPulse >= limit) : (newPulse <= limit);
if (passedLimit) {
float glowDeltaToLimit = newGlow - limit;
float glowDeltaFromLimitBack = glowDelta - glowDeltaToLimit;
glowDelta = -glowDeltaFromLimitBack;
float pulseDeltaToLimit = newPulse - limit;
float pulseDeltaFromLimitBack = pulseDelta - pulseDeltaToLimit;
pulseDelta = -pulseDeltaFromLimitBack;
_pulseDirection *= -1.0f;
}
_pulse += glowDelta;
_pulse += pulseDelta;
return _pulse;
}

View file

@ -49,7 +49,6 @@ public:
bool getVisible() const { return _visible; }
xColor getColor();
float getAlpha();
float getGlowLevel();
Anchor getAnchor() const { return _anchor; }
float getPulseMax() const { return _pulseMax; }
@ -57,7 +56,6 @@ public:
float getPulsePeriod() const { return _pulsePeriod; }
float getPulseDirection() const { return _pulseDirection; }
float getGlowLevelPulse() const { return _glowLevelPulse; }
float getColorPulse() const { return _colorPulse; }
float getAlphaPulse() const { return _alphaPulse; }
@ -65,7 +63,6 @@ public:
void setVisible(bool visible) { _visible = visible; }
void setColor(const xColor& color) { _color = color; }
void setAlpha(float alpha) { _alpha = alpha; }
void setGlowLevel(float value) { _glowLevel = value; }
void setAnchor(Anchor anchor) { _anchor = anchor; }
void setPulseMax(float value) { _pulseMax = value; }
@ -73,8 +70,6 @@ public:
void setPulsePeriod(float value) { _pulsePeriod = value; }
void setPulseDirection(float value) { _pulseDirection = value; }
void setGlowLevelPulse(float value) { _glowLevelPulse = value; }
void setColorPulse(float value) { _colorPulse = value; }
void setAlphaPulse(float value) { _alphaPulse = value; }
@ -92,7 +87,6 @@ protected:
bool _isLoaded;
float _alpha;
float _glowLevel;
float _pulse;
float _pulseMax;
@ -101,7 +95,6 @@ protected:
float _pulseDirection;
quint64 _lastPulseUpdate;
float _glowLevelPulse; // ratio of the pulse to the glow level
float _alphaPulse; // ratio of the pulse to the alpha
float _colorPulse; // ratio of the pulse to the color

View file

@ -1,3 +1,3 @@
set(TARGET_NAME animation)
setup_hifi_library(Network Script)
link_hifi_libraries(shared gpu model fbx)
link_hifi_libraries(shared model fbx)

View file

@ -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);

View file

@ -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;

View file

@ -15,7 +15,7 @@
#include "AnimationCache.h"
#include "AnimationLogging.h"
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
AnimationCache::AnimationCache(QObject* parent) :
ResourceCache(parent)

View file

@ -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];
}
}
}
}

View file

@ -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.

View file

@ -14,7 +14,7 @@
#include "AudioLogging.h"
#include "SoundCache.h"
static int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
SoundCache::SoundCache(QObject* parent) :
ResourceCache(parent)

View file

@ -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,31 +882,33 @@ void AvatarData::clearJointsData() {
}
}
bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) {
void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) {
QDataStream packetStream(data);
QUuid avatarUUID;
QUrl unusedModelURL; // legacy faceModel support
QUrl skeletonModelURL;
QVector<AttachmentData> attachmentData;
QString displayName;
packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName;
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 (identity.avatarEntityData != _avatarEntityData) {
setAvatarEntityData(identity.avatarEntityData);
hasIdentityChanged = true;
}
@ -991,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;
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();
}
@ -1167,6 +1094,8 @@ void AvatarData::sendIdentityPacket() {
[&](const SharedNodePointer& node) {
nodeList->sendPacketList(std::move(packetList), *node);
});
_avatarEntityDataLocallyEdited = false;
}
void AvatarData::updateJointMappings() {
@ -1339,6 +1268,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel");
static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel");
static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName");
static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments");
static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
static const QString JSON_AVATAR_SCALE = QStringLiteral("scale");
QJsonValue toJsonValue(const JointData& joint) {
@ -1377,6 +1307,17 @@ QJsonObject AvatarData::toJson() const {
root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson;
}
if (!_avatarEntityData.empty()) {
QJsonArray avatarEntityJson;
for (auto entityID : _avatarEntityData.keys()) {
QVariantMap entityData;
entityData.insert("id", entityID);
entityData.insert("properties", _avatarEntityData.value(entityID));
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
}
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
}
auto recordingBasis = getRecordingBasis();
bool success;
Transform avatarTransform = getTransform(success);
@ -1476,6 +1417,13 @@ void AvatarData::fromJson(const QJsonObject& json) {
setAttachmentData(attachments);
}
// if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) {
// QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray();
// for (auto attachmentJson : attachmentsJson) {
// // TODO -- something
// }
// }
// Joint rotations are relative to the avatar, so they require no basis correction
if (json.contains(JSON_AVATAR_JOINT_ARRAY)) {
QVector<JointData> jointArray;
@ -1628,9 +1576,69 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
QVector<AttachmentData> newAttachments;
newAttachments.reserve(variant.size());
for (const auto& attachmentVar : variant) {
AttachmentData attachment;
AttachmentData attachment;
attachment.fromVariant(attachmentVar);
newAttachments.append(attachment);
}
setAttachmentData(newAttachments);
}
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData));
return;
}
_avatarEntityData.insert(entityID, entityData);
_avatarEntityDataLocallyEdited = true;
}
void AvatarData::clearAvatarEntity(const QUuid& entityID) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearAvatarEntity", Q_ARG(const QUuid&, entityID));
return;
}
_avatarEntityData.remove(entityID);
_avatarEntityDataLocallyEdited = true;
}
AvatarEntityMap AvatarData::getAvatarEntityData() const {
if (QThread::currentThread() != thread()) {
AvatarEntityMap result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getAvatarEntityData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AvatarEntityMap, result));
return result;
}
return _avatarEntityData;
}
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData));
return;
}
if (_avatarEntityData != avatarEntityData) {
// keep track of entities that were attached to this avatar but no longer are
AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys());
_avatarEntityData = avatarEntityData;
setAvatarEntityDataChanged(true);
foreach (auto entityID, previousAvatarEntityIDs) {
if (!_avatarEntityData.contains(entityID)) {
_avatarEntityDetached.insert(entityID);
}
}
}
}
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
if (QThread::currentThread() != thread()) {
AvatarEntityIDs result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AvatarEntityIDs, result));
return result;
}
AvatarEntityIDs result = _avatarEntityDetached;
_avatarEntityDetached.clear();
return result;
}

View file

@ -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"
@ -61,15 +62,17 @@ typedef unsigned long long quint64;
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
using AvatarEntityMap = QMap<QUuid, QByteArray>;
using AvatarEntityIDs = QSet<QUuid>;
using AvatarDataSequenceNumber = uint16_t;
// avatar motion behaviors
const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1;
const quint32 AVATAR_MOTION_DEFAULTS =
AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED |
AVATAR_MOTION_ACTION_MOTOR_ENABLED |
AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
// these bits will be expanded as features are exposed
@ -134,6 +137,10 @@ class AttachmentData;
class Transform;
using TransformPointer = std::shared_ptr<Transform>;
// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows
// the value to be reset when the sessionID changes.
const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}");
class AvatarData : public QObject, public SpatiallyNestable {
Q_OBJECT
@ -165,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);
@ -272,6 +280,9 @@ public:
Q_INVOKABLE QVariantList getAttachmentsVariant() const;
Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant);
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID);
void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }
// key state
@ -280,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; }
@ -323,6 +346,11 @@ public:
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -390,6 +418,11 @@ protected:
// updates about one avatar to another.
glm::vec3 _globalPosition;
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
AvatarEntityMap _avatarEntityData;
bool _avatarEntityDataLocallyEdited { false };
bool _avatarEntityDataChanged { false };
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
static QUrl _defaultFullAvatarModelUrl;

View file

@ -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,33 +107,12 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<ReceivedMessage> mess
}
void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// 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;
QString displayName;
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName;
// 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);
}
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) {
@ -144,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);
}

View file

@ -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) {
}
}
}

View file

@ -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&);

View file

@ -121,16 +121,8 @@ namespace controller {
return availableInputs;
}
void ActionsDevice::update(float deltaTime, const InputCalibrationData& inpuCalibrationData, bool jointsCaptured) {
}
void ActionsDevice::focusOutEvent() {
}
ActionsDevice::ActionsDevice() : InputDevice("Actions") {
_deviceID = UserInputMapper::ACTIONS_DEVICE;
}
ActionsDevice::~ActionsDevice() {}
}

View file

@ -109,13 +109,11 @@ class ActionsDevice : public QObject, public InputDevice {
Q_PROPERTY(QString name READ getName)
public:
virtual EndpointPointer createEndpoint(const Input& input) const override;
virtual Input::NamedVector getAvailableInputs() const override;
virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override;
virtual void focusOutEvent() override;
ActionsDevice();
virtual ~ActionsDevice();
EndpointPointer createEndpoint(const Input& input) const override;
Input::NamedVector getAvailableInputs() const override;
};
}

View file

@ -57,9 +57,9 @@ public:
// Update call MUST be called once per simulation loop
// It takes care of updating the action states and deltas
virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) = 0;
virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData) {};
virtual void focusOutEvent() = 0;
virtual void focusOutEvent() {};
int getDeviceID() { return _deviceID; }
void setDeviceID(int deviceID) { _deviceID = deviceID; }

View file

@ -22,12 +22,6 @@ StandardController::StandardController() : InputDevice("Standard") {
_deviceID = UserInputMapper::STANDARD_DEVICE;
}
StandardController::~StandardController() {
}
void StandardController::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) {
}
void StandardController::focusOutEvent() {
_axisStateMap.clear();
_buttonPressedMap.clear();

View file

@ -28,11 +28,9 @@ public:
virtual EndpointPointer createEndpoint(const Input& input) const override;
virtual Input::NamedVector getAvailableInputs() const override;
virtual QStringList getDefaultMappingConfigs() const override;
virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override;
virtual void focusOutEvent() override;
StandardController();
virtual ~StandardController();
};
}

View file

@ -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,

View file

@ -35,10 +35,7 @@ public:
const QString& getName() const { return _name; }
// Device functions
virtual Input::NamedVector getAvailableInputs() const override;
void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override {}
void focusOutEvent() override {}
Input::NamedVector getAvailableInputs() const override;
void setInputVariant(const QString& name, ReadLambda lambda);

View file

@ -336,10 +336,10 @@ QVector<QString> UserInputMapper::getActionNames() const {
return result;
}
static int actionMetaTypeId = qRegisterMetaType<Action>();
static int inputMetaTypeId = qRegisterMetaType<Input>();
static int inputPairMetaTypeId = qRegisterMetaType<Input::NamedPair>();
static int poseMetaTypeId = qRegisterMetaType<controller::Pose>("Pose");
int actionMetaTypeId = qRegisterMetaType<Action>();
int inputMetaTypeId = qRegisterMetaType<Input>();
int inputPairMetaTypeId = qRegisterMetaType<Input::NamedPair>();
int poseMetaTypeId = qRegisterMetaType<controller::Pose>("Pose");
QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input);
void inputFromScriptValue(const QScriptValue& object, Input& input);

View file

@ -1,6 +1,6 @@
set(TARGET_NAME display-plugins)
setup_hifi_library(OpenGL)
link_hifi_libraries(shared plugins gl ui)
link_hifi_libraries(shared plugins gl gpu-gl ui)
target_opengl()

View file

@ -17,7 +17,7 @@
#include <QtGui/QImage>
#include <gl/QOpenGLContextWrapper.h>
#include <gpu/Texture.h>
#include <gl/GLWidget.h>
#include <NumericalConstants.h>
#include <DependencyManager.h>
@ -26,7 +26,6 @@
#include <gl/Config.h>
#include <gl/GLEscrow.h>
#include <GLMHelpers.h>
#include <gpu/GLBackend.h>
#include <CursorManager.h>
#include "CompositorHelper.h"
@ -629,14 +628,15 @@ uint32_t OpenGLDisplayPlugin::getSceneTextureId() const {
if (!_currentSceneTexture) {
return 0;
}
return gpu::GLBackend::getTextureID(_currentSceneTexture, false);
return _currentSceneTexture->getHardwareId();
}
uint32_t OpenGLDisplayPlugin::getOverlayTextureId() const {
if (!_currentOverlayTexture) {
return 0;
}
return gpu::GLBackend::getTextureID(_currentOverlayTexture, false);
return _currentOverlayTexture->getHardwareId();
}
void OpenGLDisplayPlugin::eyeViewport(Eye eye) const {

View file

@ -16,7 +16,6 @@
#include <GLMHelpers.h>
#include <plugins/PluginContainer.h>
#include <gpu/GLBackend.h>
#include <CursorManager.h>
#include <gl/GLWidget.h>
#include <shared/NsightHelpers.h>

View file

@ -13,7 +13,6 @@
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <gpu/GLBackend.h>
#include <ViewFrustum.h>
#include <MatrixStack.h>
#include <plugins/PluginContainer.h>

View file

@ -1,7 +1,7 @@
set(TARGET_NAME entities-renderer)
AUTOSCRIBE_SHADER_LIB(gpu model render render-utils)
setup_hifi_library(Widgets Network Script)
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils gl)
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils)
target_bullet()

View file

@ -13,6 +13,7 @@
#include <QEventLoop>
#include <QScriptSyntaxCheckResult>
#include <QThreadPool>
#include <ColorUtils.h>
#include <AbstractScriptingServicesInterface.h>
@ -28,17 +29,16 @@
#include "RenderableEntityItem.h"
#include "RenderableBoxEntityItem.h"
#include "RenderableLightEntityItem.h"
#include "RenderableModelEntityItem.h"
#include "RenderableParticleEffectEntityItem.h"
#include "RenderableSphereEntityItem.h"
#include "RenderableTextEntityItem.h"
#include "RenderableWebEntityItem.h"
#include "RenderableZoneEntityItem.h"
#include "RenderableLineEntityItem.h"
#include "RenderablePolyVoxEntityItem.h"
#include "RenderablePolyLineEntityItem.h"
#include "RenderableShapeEntityItem.h"
#include "EntitiesRendererLogging.h"
#include "AddressManager.h"
#include <Rig.h>
@ -55,8 +55,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
_dontDoPrecisionPicking(false)
{
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, RenderableWebEntityItem::factory)
@ -65,7 +63,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyLine, RenderablePolyLineEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Shape, RenderableShapeEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableShapeEntityItem::boxFactory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableShapeEntityItem::sphereFactory)
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID;
_currentClickingOnEntityID = UNKNOWN_ENTITY_ID;
}
@ -77,9 +78,30 @@ EntityTreeRenderer::~EntityTreeRenderer() {
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
void EntityTreeRenderer::setupEntitiesScriptEngine() {
QSharedPointer<ScriptEngine> oldEngine = _entitiesScriptEngine; // save the old engine through this function, so the EntityScriptingInterface doesn't have problems with it.
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)), &QObject::deleteLater);
void entitiesScriptEngineDeleter(ScriptEngine* engine) {
class WaitRunnable : public QRunnable {
public:
WaitRunnable(ScriptEngine* engine) : _engine(engine) {}
virtual void run() override {
_engine->waitTillDoneRunning();
_engine->deleteLater();
}
private:
ScriptEngine* _engine;
};
// Wait for the scripting thread from the thread pool to avoid hanging the main thread
QThreadPool::globalInstance()->start(new WaitRunnable(engine));
}
void EntityTreeRenderer::resetEntitiesScriptEngine() {
// Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use
auto oldEngine = _entitiesScriptEngine;
auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount));
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(newEngine, entitiesScriptEngineDeleter);
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
_entitiesScriptEngine->runInThread();
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine.data());
@ -87,16 +109,16 @@ void EntityTreeRenderer::setupEntitiesScriptEngine() {
void EntityTreeRenderer::clear() {
leaveAllEntities();
if (_entitiesScriptEngine) {
// Unload and stop the engine here (instead of in its deleter) to
// avoid marshalling unload signals back to this thread
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
if (_wantScripts && !_shuttingDown) {
// NOTE: you can't actually need to delete it here because when we call setupEntitiesScriptEngine it will
// assign a new instance to our shared pointer, which will deref the old instance and ultimately call
// the custom deleter which calls deleteLater
setupEntitiesScriptEngine();
resetEntitiesScriptEngine();
}
auto scene = _viewState->getMain3DScene();
@ -125,7 +147,7 @@ void EntityTreeRenderer::init() {
entityTree->setFBXService(this);
if (_wantScripts) {
setupEntitiesScriptEngine();
resetEntitiesScriptEngine();
}
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities

View file

@ -88,6 +88,8 @@ public:
// For Scene.shouldRenderEntities
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
std::shared_ptr<ZoneEntityItem> myAvatarZone() { return _bestZone; }
signals:
void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
@ -126,7 +128,7 @@ protected:
}
private:
void setupEntitiesScriptEngine();
void resetEntitiesScriptEngine();
void addEntityToScene(EntityItemPointer entity);
bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar);

View file

@ -1,76 +0,0 @@
//
// RenderableBoxEntityItem.cpp
// libraries/entities-renderer/src/
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RenderableBoxEntityItem.h"
#include <glm/gtx/quaternion.hpp>
#include <gpu/Batch.h>
#include <GeometryCache.h>
#include <ObjectMotionState.h>
#include <PerfStat.h>
#include <render-utils/simple_vert.h>
#include <render-utils/simple_frag.h>
EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableBoxEntityItem(entityID) };
entity->setProperties(properties);
return entity;
}
void RenderableBoxEntityItem::setUserData(const QString& value) {
if (value != getUserData()) {
BoxEntityItem::setUserData(value);
if (_procedural) {
_procedural->parse(value);
}
}
}
void RenderableBoxEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
Q_ASSERT(getType() == EntityTypes::Box);
Q_ASSERT(args->_batch);
if (!_procedural) {
_procedural.reset(new Procedural(this->getUserData()));
_procedural->_vertexSource = simple_vert;
_procedural->_fragmentSource = simple_frag;
_procedural->_state->setCullMode(gpu::State::CULL_NONE);
_procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
_procedural->_state->setBlendFunction(false,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
}
gpu::Batch& batch = *args->_batch;
glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());
bool success;
auto transToCenter = getTransformToCenter(success);
if (!success) {
return;
}
batch.setModelTransform(transToCenter); // we want to include the scale as well
if (_procedural->ready()) {
_procedural->prepare(batch, getPosition(), getDimensions());
auto color = _procedural->getColor(cubeColor);
batch._glColor4f(color.r, color.g, color.b, color.a);
DependencyManager::get<GeometryCache>()->renderCube(batch);
} else {
DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(batch, cubeColor);
}
static const auto triCount = DependencyManager::get<GeometryCache>()->getCubeTriangleCount();
args->_details._trianglesRendered += (int)triCount;
}

View file

@ -1,34 +0,0 @@
//
// RenderableBoxEntityItem.h
// libraries/entities-renderer/src/
//
// Created by Brad Hefta-Gaub on 8/6/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RenderableBoxEntityItem_h
#define hifi_RenderableBoxEntityItem_h
#include <BoxEntityItem.h>
#include <procedural/Procedural.h>
#include "RenderableEntityItem.h"
class RenderableBoxEntityItem : public BoxEntityItem {
public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
RenderableBoxEntityItem(const EntityItemID& entityItemID) : BoxEntityItem(entityItemID) { }
virtual void render(RenderArgs* args) override;
virtual void setUserData(const QString& value) override;
SIMPLE_RENDERABLE()
private:
QSharedPointer<Procedural> _procedural;
};
#endif // hifi_RenderableBoxEntityItem_h

View file

@ -47,6 +47,9 @@ namespace render {
}
void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid& myNodeID = nodeList->getSessionUUID();
statusGetters.push_back([entity] () -> render::Item::Status::Value {
quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote();
const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND);
@ -81,9 +84,7 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status:
(unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET);
});
statusGetters.push_back([entity] () -> render::Item::Status::Value {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid& myNodeID = nodeList->getSessionUUID();
statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value {
bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID);
bool otherOwnSimulation = !weOwnSimulation && !entity->getSimulationOwner().isNull();
@ -106,4 +107,18 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status:
return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN,
(unsigned char)RenderItemStatusIcon::HAS_ACTIONS);
});
statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value {
if (entity->getClientOnly()) {
if (entity->getOwningAvatarID() == myNodeID) {
return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN,
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
} else {
return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED,
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
}
}
return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN,
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
});
}

View file

@ -26,6 +26,7 @@ enum class RenderItemStatusIcon {
SIMULATION_OWNER = 3,
HAS_ACTIONS = 4,
OTHER_SIMULATION_OWNER = 5,
CLIENT_ONLY = 6,
NONE = 255
};

View file

@ -48,13 +48,6 @@ RenderableModelEntityItem::~RenderableModelEntityItem() {
void RenderableModelEntityItem::setModelURL(const QString& url) {
auto& currentURL = getParsedModelURL();
if (_model && (currentURL != url)) {
// The machinery for updateModelBounds will give existing models the opportunity to fix their translation/rotation/scale/registration.
// The first two are straightforward, but the latter two have guards to make sure they don't happen after they've already been set.
// Here we reset those guards. This doesn't cause the entity values to change -- it just allows the model to match once it comes in.
_model->setScaleToFit(false, getDimensions());
_model->setSnapModelToRegistrationPoint(false, getRegistrationPoint());
}
ModelEntityItem::setModelURL(url);
if (currentURL != getParsedModelURL() || !_model) {
@ -162,6 +155,27 @@ void RenderableModelEntityItem::remapTextures() {
}
}
void RenderableModelEntityItem::doInitialModelSimulation() {
// The machinery for updateModelBounds will give existing models the opportunity to fix their
// translation/rotation/scale/registration. The first two are straightforward, but the latter two have guards to
// make sure they don't happen after they've already been set. Here we reset those guards. This doesn't cause the
// entity values to change -- it just allows the model to match once it comes in.
_model->setScaleToFit(false, getDimensions());
_model->setSnapModelToRegistrationPoint(false, getRegistrationPoint());
// now recalculate the bounds and registration
_model->setScaleToFit(true, getDimensions());
_model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
_model->setRotation(getRotation());
_model->setTranslation(getPosition());
{
PerformanceTimer perfTimer("_model->simulate");
_model->simulate(0.0f);
}
_needsInitialSimulation = false;
}
// TODO: we need a solution for changes to the postion/rotation/etc of a model...
// this current code path only addresses that in this setup case... not the changing/moving case
bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) {
@ -172,22 +186,12 @@ bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) {
getModel(renderer);
}
if (renderArgs && _model && _needsInitialSimulation && _model->isActive() && _model->isLoaded()) {
_model->setScaleToFit(true, getDimensions());
_model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
_model->setRotation(getRotation());
_model->setTranslation(getPosition());
// make sure to simulate so everything gets set up correctly for rendering
{
PerformanceTimer perfTimer("_model->simulate");
_model->simulate(0.0f);
}
_needsInitialSimulation = false;
doInitialModelSimulation();
_model->renderSetup(renderArgs);
}
bool ready = !_needsInitialSimulation && _model && _model->readyToAddToScene(renderArgs);
return ready;
return ready;
}
class RenderableModelEntityItemMeta {
@ -348,18 +352,7 @@ void RenderableModelEntityItem::updateModelBounds() {
_model->getRotation() != getRotation() ||
_model->getRegistrationPoint() != getRegistrationPoint())
&& _model->isActive() && _dimensionsInitialized) {
_model->setScaleToFit(true, dimensions);
_model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
_model->setRotation(getRotation());
_model->setTranslation(getPosition());
// make sure to simulate so everything gets set up correctly for rendering
{
PerformanceTimer perfTimer("_model->simulate");
_model->simulate(0.0f);
}
_needsInitialSimulation = false;
doInitialModelSimulation();
_needsJointSimulation = false;
}
}
@ -592,8 +585,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
PerformanceTimer perfTimer("_model->simulate");
_model->simulate(0.0f);
_needsInitialSimulation = false;
doInitialModelSimulation();
}
return true;
@ -807,6 +799,29 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) {
if (_model && _model->isActive()) {
_model->setRotation(getRotation());
_model->setTranslation(getPosition());
void* key = (void*)this;
std::weak_ptr<RenderableModelEntityItem> weakSelf =
std::static_pointer_cast<RenderableModelEntityItem>(getThisPointer());
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() {
auto self = weakSelf.lock();
if (!self) {
return;
}
render::ItemID myMetaItem = self->getMetaRenderItem();
if (myMetaItem == render::Item::INVALID_ITEM_ID) {
return;
}
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
render::PendingChanges pendingChanges;
pendingChanges.updateItem(myMetaItem);
scene->enqueuePendingChanges(pendingChanges);
});
}
}

View file

@ -38,6 +38,8 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
void doInitialModelSimulation();
virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr);
virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;
@ -87,6 +89,8 @@ public:
bool hasRenderAnimation() const { return !_renderAnimationProperties.getURL().isEmpty(); }
const QString& getRenderAnimationURL() const { return _renderAnimationProperties.getURL(); }
render::ItemID getMetaRenderItem() { return _myMetaItem; }
private:
QVariantMap parseTexturesToMap(QString textures);
void remapTextures();

Some files were not shown because too many files have changed in this diff Show more