mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 00:24:13 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into color
This commit is contained in:
commit
25eb569433
351 changed files with 11385 additions and 9272 deletions
LICENSE
assignment-client/src
cmake/macros
domain-server/src
DomainGatekeeper.cppDomainGatekeeper.hDomainServer.cppDomainServer.hDomainServerNodeData.hNodeConnectionData.cppNodeConnectionData.h
interface
CMakeLists.txt
resources
controllers
fonts
qml
controls-uit
dialogs
hifi
styles-uit
windows-uit
shaders
src
libraries
animation
audio/src
avatars/src
controllers/src/controllers
Actions.cppActions.hInputDevice.hStandardController.cppStandardController.hStandardControls.hStateController.hUserInputMapper.cpp
display-plugins
CMakeLists.txt
src/display-plugins
entities-renderer
2
LICENSE
2
LICENSE
|
@ -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.
|
||||
|
|
|
@ -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.";
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket");
|
||||
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);
|
||||
}
|
||||
|
||||
AvatarMixer::~AvatarMixer() {
|
||||
|
@ -414,7 +417,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
|||
AvatarData& avatar = nodeData->getAvatar();
|
||||
|
||||
// parse the identity packet and update the change timestamp if appropriate
|
||||
if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) {
|
||||
AvatarData::Identity identity;
|
||||
AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity);
|
||||
if (avatar.processAvatarIdentity(identity)) {
|
||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||
nodeData->flagIdentityChange();
|
||||
}
|
||||
|
@ -509,6 +514,19 @@ void AvatarMixer::domainSettingsRequestComplete() {
|
|||
_broadcastThread.start();
|
||||
}
|
||||
|
||||
void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID) {
|
||||
// if this client is using packet versions we don't expect.
|
||||
if ((type == PacketTypeEnum::Value::AvatarIdentity || type == PacketTypeEnum::Value::AvatarData) && !senderUUID.isNull()) {
|
||||
// Echo an empty AvatarData packet back to that client.
|
||||
// This should trigger a version mismatch dialog on their side.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto node = nodeList->nodeWithUUID(senderUUID);
|
||||
if (node) {
|
||||
auto emptyPacket = NLPacket::create(PacketType::AvatarData, 0);
|
||||
nodeList->sendPacket(std::move(emptyPacket), *node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||
const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer";
|
||||
|
|
|
@ -38,7 +38,8 @@ private slots:
|
|||
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void domainSettingsRequestComplete();
|
||||
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
|
||||
private:
|
||||
void broadcastAvatarData();
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ public:
|
|||
HifiSockAddr localSockAddr;
|
||||
HifiSockAddr senderSockAddr;
|
||||
QList<NodeType_t> interestList;
|
||||
QString placeName;
|
||||
|
||||
QByteArray protocolVersion;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
{ "from": "Hydra.L0", "to": "Standard.Back" },
|
||||
{ "from": "Hydra.R0", "to": "Standard.Start" },
|
||||
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.L1", "Hydra.L3" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Neuron to Standard",
|
||||
"channels": [
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
{ "from": "Neuron.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Neuron.RightHand", "to": "Standard.RightHand" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" }
|
||||
|
|
BIN
interface/resources/fonts/FiraSans-Regular.ttf
Normal file
BIN
interface/resources/fonts/FiraSans-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ FocusScope {
|
|||
HiFiGlyphs {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -8
|
||||
topMargin: -11
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
size: hifi.dimensions.spinnerSize
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
23
interface/resources/qml/styles-uit/FiraSansRegular.qml
Normal file
23
interface/resources/qml/styles-uit/FiraSansRegular.qml
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -20,6 +20,14 @@ Frame {
|
|||
Rectangle {
|
||||
// Dialog frame
|
||||
id: frameContent
|
||||
|
||||
readonly property int iconSize: hifi.dimensions.frameIconSize
|
||||
readonly property int frameMargin: 9
|
||||
readonly property int frameMarginLeft: frameMargin
|
||||
readonly property int frameMarginRight: frameMargin
|
||||
readonly property int frameMarginTop: 2 * frameMargin + iconSize
|
||||
readonly property int frameMarginBottom: iconSize + 11
|
||||
|
||||
anchors {
|
||||
topMargin: -frameMarginTop
|
||||
leftMargin: -frameMarginLeft
|
||||
|
@ -34,7 +42,7 @@ Frame {
|
|||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
|
||||
// Allow dragging of the window
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
|
@ -45,17 +53,17 @@ Frame {
|
|||
anchors {
|
||||
right: parent.right;
|
||||
top: parent.top;
|
||||
topMargin: frameMargin + 1 // Move down a little to visually align with the title
|
||||
rightMargin: frameMarginRight;
|
||||
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
|
||||
rightMargin: frameContent.frameMarginRight;
|
||||
}
|
||||
spacing: iconSize / 4
|
||||
spacing: frameContent.iconSize / 4
|
||||
|
||||
HiFiGlyphs {
|
||||
// "Pin" button
|
||||
visible: false
|
||||
text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin
|
||||
color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
|
||||
size: iconSize
|
||||
size: frameContent.iconSize
|
||||
MouseArea {
|
||||
id: pinClickArea
|
||||
anchors.fill: parent
|
||||
|
@ -70,7 +78,7 @@ Frame {
|
|||
visible: window ? window.closable : false
|
||||
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
|
||||
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
|
||||
size: iconSize
|
||||
size: frameContent.iconSize
|
||||
MouseArea {
|
||||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
|
@ -85,11 +93,11 @@ Frame {
|
|||
id: titleText
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: frameMarginLeft + hifi.dimensions.contentMargin.x
|
||||
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
|
||||
right: controlsRow.left
|
||||
rightMargin: iconSize
|
||||
rightMargin: frameContent.iconSize
|
||||
top: parent.top
|
||||
topMargin: frameMargin
|
||||
topMargin: frameContent.frameMargin
|
||||
}
|
||||
text: window ? window.title : ""
|
||||
color: hifi.colors.white
|
||||
|
|
|
@ -22,12 +22,10 @@ Item {
|
|||
|
||||
property bool gradientsSupported: desktop.gradientsSupported
|
||||
|
||||
readonly property int iconSize: 22
|
||||
readonly property int frameMargin: 9
|
||||
readonly property int frameMarginLeft: frameMargin
|
||||
readonly property int frameMarginRight: frameMargin
|
||||
readonly property int frameMarginTop: 2 * frameMargin + iconSize
|
||||
readonly property int frameMarginBottom: iconSize + 11
|
||||
readonly property int frameMarginLeft: frameContent.frameMarginLeft
|
||||
readonly property int frameMarginRight: frameContent.frameMarginRight
|
||||
readonly property int frameMarginTop: frameContent.frameMarginTop
|
||||
readonly property int frameMarginBottom: frameContent.frameMarginBottom
|
||||
|
||||
// Frames always fill their parents, but their decorations may extend
|
||||
// beyond the window via negative margin sizes
|
||||
|
@ -76,8 +74,8 @@ Item {
|
|||
id: sizeOutline
|
||||
x: -frameMarginLeft
|
||||
y: -frameMarginTop
|
||||
width: window ? window.width + frameMarginLeft + frameMarginRight : 0
|
||||
height: window ? window.height + frameMarginTop + frameMarginBottom : 0
|
||||
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
|
||||
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
|
||||
color: hifi.colors.baseGrayHighlight15
|
||||
border.width: 3
|
||||
border.color: hifi.colors.white50
|
||||
|
@ -88,11 +86,11 @@ Item {
|
|||
MouseArea {
|
||||
// Resize handle
|
||||
id: sizeDrag
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
width: hifi.dimensions.frameIconSize
|
||||
height: hifi.dimensions.frameIconSize
|
||||
enabled: window ? window.resizable : false
|
||||
hoverEnabled: true
|
||||
x: window ? window.width + frameMarginRight - iconSize : 0
|
||||
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
|
||||
y: window ? window.height + 4 : 0
|
||||
property vector2d pressOrigin
|
||||
property vector2d sizeOrigin
|
||||
|
@ -124,10 +122,12 @@ Item {
|
|||
HiFiGlyphs {
|
||||
visible: sizeDrag.enabled
|
||||
x: -11 // Move a little to visually align
|
||||
y: -4 // ""
|
||||
y: window.modality == Qt.ApplicationModal ? -6 : -4
|
||||
text: hifi.glyphs.resizeHandle
|
||||
size: iconSize + 10
|
||||
color: sizeDrag.containsMouse || sizeDrag.pressed ? hifi.colors.white : hifi.colors.white50
|
||||
size: hifi.dimensions.frameIconSize + 10
|
||||
color: sizeDrag.containsMouse || sizeDrag.pressed
|
||||
? hifi.colors.white
|
||||
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,16 +18,21 @@ Frame {
|
|||
HifiConstants { id: hifi }
|
||||
|
||||
Rectangle {
|
||||
id: modalFrame
|
||||
id: frameContent
|
||||
|
||||
readonly property bool hasTitle: window.title != ""
|
||||
|
||||
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
|
||||
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
|
||||
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
|
||||
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: -hifi.dimensions.modalDialogMargin.y - (modalFrame.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
|
||||
leftMargin: -hifi.dimensions.modalDialogMargin.x
|
||||
rightMargin: -hifi.dimensions.modalDialogMargin.x
|
||||
bottomMargin: -hifi.dimensions.modalDialogMargin.y
|
||||
topMargin: -frameMarginTop
|
||||
leftMargin: -frameMarginLeft
|
||||
rightMargin: -frameMarginRight
|
||||
bottomMargin: -frameMarginBottom
|
||||
}
|
||||
|
||||
border {
|
||||
|
@ -37,8 +42,15 @@ Frame {
|
|||
radius: hifi.dimensions.borderRadius
|
||||
color: hifi.colors.faintGray
|
||||
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
enabled: window.draggable
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: modalFrame.hasTitle
|
||||
visible: frameContent.hasTitle
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
topMargin: -parent.anchors.topMargin
|
||||
|
|
|
@ -14,9 +14,13 @@ import "."
|
|||
|
||||
Window {
|
||||
id: window
|
||||
anchors.centerIn: parent
|
||||
modality: Qt.ApplicationModal
|
||||
destroyOnCloseButton: true
|
||||
destroyOnInvisible: true
|
||||
frame: ModalFrame{}
|
||||
frame: ModalFrame { }
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
property bool draggable: false
|
||||
|
||||
anchors.centerIn: draggable ? undefined : parent
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ Fadable {
|
|||
// property bool pinned: false
|
||||
property bool resizable: false
|
||||
property bool gradientsSupported: desktop.gradientsSupported
|
||||
property int colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
property vector2d minSize: Qt.vector2d(100, 100)
|
||||
property vector2d maxSize: Qt.vector2d(1280, 800)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -33,7 +33,6 @@ signals:
|
|||
protected:
|
||||
void displayAddressOfflineMessage();
|
||||
void displayAddressNotFoundMessage();
|
||||
void hide();
|
||||
|
||||
Q_INVOKABLE void loadAddress(const QString& address);
|
||||
Q_INVOKABLE void loadHome();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -107,6 +107,18 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const {
|
|||
}
|
||||
}
|
||||
|
||||
void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector<glm::quat>& rotations) const {
|
||||
// poses start off absolute and leave in relative frame
|
||||
int lastIndex = std::min((int)rotations.size(), (int)_joints.size());
|
||||
for (int i = lastIndex - 1; i >= 0; --i) {
|
||||
int parentIndex = _joints[i].parentIndex;
|
||||
if (parentIndex != -1) {
|
||||
rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const {
|
||||
convertRelativePosesToAbsolute(poses);
|
||||
mirrorAbsolutePoses(poses);
|
||||
|
|
|
@ -55,6 +55,8 @@ public:
|
|||
void convertRelativePosesToAbsolute(AnimPoseVec& poses) const;
|
||||
void convertAbsolutePosesToRelative(AnimPoseVec& poses) const;
|
||||
|
||||
void convertAbsoluteRotationsToRelative(std::vector<glm::quat>& rotations) const;
|
||||
|
||||
void mirrorRelativePoses(AnimPoseVec& poses) const;
|
||||
void mirrorAbsolutePoses(AnimPoseVec& poses) const;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -165,6 +165,7 @@ void Rig::destroyAnimGraph() {
|
|||
void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) {
|
||||
|
||||
_geometryOffset = AnimPose(geometry.offset);
|
||||
_invGeometryOffset = _geometryOffset.inverse();
|
||||
setModelOffset(modelOffset);
|
||||
|
||||
_animSkeleton = std::make_shared<AnimSkeleton>(geometry);
|
||||
|
@ -193,6 +194,7 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff
|
|||
|
||||
void Rig::reset(const FBXGeometry& geometry) {
|
||||
_geometryOffset = AnimPose(geometry.offset);
|
||||
_invGeometryOffset = _geometryOffset.inverse();
|
||||
_animSkeleton = std::make_shared<AnimSkeleton>(geometry);
|
||||
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
|
@ -272,24 +274,6 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Rig::getJointStateRotation(int index, glm::quat& rotation) const {
|
||||
if (isIndexValid(index)) {
|
||||
rotation = _internalPoseSet._relativePoses[index].rot;
|
||||
return !isEqual(rotation, _animSkeleton->getRelativeDefaultPose(index).rot);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const {
|
||||
if (isIndexValid(index)) {
|
||||
translation = _internalPoseSet._relativePoses[index].trans;
|
||||
return !isEqual(translation, _animSkeleton->getRelativeDefaultPose(index).trans);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::clearJointState(int index) {
|
||||
if (isIndexValid(index)) {
|
||||
_internalPoseSet._overrideFlags[index] = false;
|
||||
|
@ -1229,24 +1213,90 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const {
|
|||
}
|
||||
|
||||
void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
|
||||
|
||||
const AnimPose geometryToRigPose(_geometryToRigTransform);
|
||||
|
||||
jointDataVec.resize((int)getJointStateCount());
|
||||
for (auto i = 0; i < jointDataVec.size(); i++) {
|
||||
JointData& data = jointDataVec[i];
|
||||
data.rotationSet |= getJointStateRotation(i, data.rotation);
|
||||
// geometry offset is used here so that translations are in meters.
|
||||
// this is what the avatar mixer expects
|
||||
data.translationSet |= getJointStateTranslation(i, data.translation);
|
||||
data.translation = _geometryOffset * data.translation;
|
||||
if (isIndexValid(i)) {
|
||||
// rotations are in absolute rig frame.
|
||||
glm::quat defaultAbsRot = geometryToRigPose.rot * _animSkeleton->getAbsoluteDefaultPose(i).rot;
|
||||
data.rotation = _internalPoseSet._absolutePoses[i].rot;
|
||||
data.rotationSet = !isEqual(data.rotation, defaultAbsRot);
|
||||
|
||||
// translations are in relative frame but scaled so that they are in meters,
|
||||
// instead of geometry units.
|
||||
glm::vec3 defaultRelTrans = _geometryOffset.scale * _animSkeleton->getRelativeDefaultPose(i).trans;
|
||||
data.translation = _geometryOffset.scale * _internalPoseSet._relativePoses[i].trans;
|
||||
data.translationSet = !isEqual(data.translation, defaultRelTrans);
|
||||
} else {
|
||||
data.translationSet = false;
|
||||
data.rotationSet = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
|
||||
AnimPose invGeometryOffset = _geometryOffset.inverse();
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
const JointData& data = jointDataVec.at(i);
|
||||
setJointRotation(i, data.rotationSet, data.rotation, 1.0f);
|
||||
// geometry offset is used here to undo the fact that avatar mixer translations are in meters.
|
||||
setJointTranslation(i, data.translationSet, invGeometryOffset * data.translation, 1.0f);
|
||||
|
||||
if (_animSkeleton) {
|
||||
|
||||
// transform all the default poses into rig space.
|
||||
const AnimPose geometryToRigPose(_geometryToRigTransform);
|
||||
std::vector<bool> overrideFlags(_internalPoseSet._overridePoses.size(), false);
|
||||
|
||||
// start with the default rotations in absolute rig frame
|
||||
std::vector<glm::quat> rotations;
|
||||
rotations.reserve(_animSkeleton->getAbsoluteDefaultPoses().size());
|
||||
for (auto& pose : _animSkeleton->getAbsoluteDefaultPoses()) {
|
||||
rotations.push_back(geometryToRigPose.rot * pose.rot);
|
||||
}
|
||||
|
||||
// start translations in relative frame but scaled to meters.
|
||||
std::vector<glm::vec3> translations;
|
||||
translations.reserve(_animSkeleton->getRelativeDefaultPoses().size());
|
||||
for (auto& pose : _animSkeleton->getRelativeDefaultPoses()) {
|
||||
translations.push_back(_geometryOffset.scale * pose.trans);
|
||||
}
|
||||
|
||||
ASSERT(overrideFlags.size() == rotations.size());
|
||||
|
||||
// copy over rotations from the jointDataVec, which is also in absolute rig frame
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
if (isIndexValid(i)) {
|
||||
const JointData& data = jointDataVec.at(i);
|
||||
if (data.rotationSet) {
|
||||
overrideFlags[i] = true;
|
||||
rotations[i] = data.rotation;
|
||||
}
|
||||
if (data.translationSet) {
|
||||
overrideFlags[i] = true;
|
||||
translations[i] = data.translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size());
|
||||
|
||||
// convert resulting rotations into geometry space.
|
||||
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
|
||||
for (auto& rot : rotations) {
|
||||
rot = rigToGeometryRot * rot;
|
||||
}
|
||||
|
||||
// convert all rotations from absolute to parent relative.
|
||||
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
|
||||
|
||||
// copy the geometry space parent relative poses into _overridePoses
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
if (overrideFlags[i]) {
|
||||
_internalPoseSet._overrideFlags[i] = true;
|
||||
_internalPoseSet._overridePoses[i].scale = Vectors::ONE;
|
||||
_internalPoseSet._overridePoses[i].rot = rotations[i];
|
||||
// scale translations from meters back into geometry units.
|
||||
_internalPoseSet._overridePoses[i].trans = _invGeometryOffset.scale * translations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,12 +104,6 @@ public:
|
|||
|
||||
void setModelOffset(const glm::mat4& modelOffsetMat);
|
||||
|
||||
// geometry space
|
||||
bool getJointStateRotation(int index, glm::quat& rotation) const;
|
||||
|
||||
// geometry space
|
||||
bool getJointStateTranslation(int index, glm::vec3& translation) const;
|
||||
|
||||
void clearJointState(int index);
|
||||
void clearJointStates();
|
||||
void clearJointAnimationPriority(int index);
|
||||
|
@ -119,8 +113,6 @@ public:
|
|||
|
||||
// geometry space
|
||||
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
|
||||
|
||||
// geometry space
|
||||
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
|
||||
|
||||
// legacy
|
||||
|
@ -239,6 +231,7 @@ protected:
|
|||
|
||||
AnimPose _modelOffset; // model to rig space
|
||||
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)
|
||||
AnimPose _invGeometryOffset;
|
||||
|
||||
struct PoseSet {
|
||||
AnimPoseVec _relativePoses; // geometry space relative to parent.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -43,10 +43,9 @@ HeadData::HeadData(AvatarData* owningAvatar) :
|
|||
_averageLoudness(0.0f),
|
||||
_browAudioLift(0.0f),
|
||||
_audioAverageLoudness(0.0f),
|
||||
_pupilDilation(0.0f),
|
||||
_owningAvatar(owningAvatar)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
glm::quat HeadData::getRawOrientation() const {
|
||||
|
@ -72,7 +71,7 @@ void HeadData::setOrientation(const glm::quat& orientation) {
|
|||
glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT);
|
||||
bodyOrientation = bodyOrientation * glm::angleAxis(atan2f(-newFront.x, -newFront.z), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
_owningAvatar->setOrientation(bodyOrientation);
|
||||
|
||||
|
||||
// the rest goes to the head
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation));
|
||||
_basePitch = eulers.x;
|
||||
|
@ -186,4 +185,3 @@ void HeadData::fromJson(const QJsonObject& json) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class HeadData {
|
|||
public:
|
||||
explicit HeadData(AvatarData* owningAvatar);
|
||||
virtual ~HeadData() { };
|
||||
|
||||
|
||||
// degrees
|
||||
float getBaseYaw() const { return _baseYaw; }
|
||||
void setBaseYaw(float yaw) { _baseYaw = glm::clamp(yaw, MIN_HEAD_YAW, MAX_HEAD_YAW); }
|
||||
|
@ -42,7 +42,7 @@ public:
|
|||
void setBasePitch(float pitch) { _basePitch = glm::clamp(pitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH); }
|
||||
float getBaseRoll() const { return _baseRoll; }
|
||||
void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); }
|
||||
|
||||
|
||||
virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; }
|
||||
virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; }
|
||||
virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; }
|
||||
|
@ -64,26 +64,23 @@ public:
|
|||
void setBlendshape(QString name, float val);
|
||||
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
|
||||
|
||||
float getPupilDilation() const { return _pupilDilation; }
|
||||
void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; }
|
||||
|
||||
|
||||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; }
|
||||
|
||||
|
||||
|
||||
|
||||
float getLeanSideways() const { return _leanSideways; }
|
||||
float getLeanForward() const { return _leanForward; }
|
||||
float getTorsoTwist() const { return _torsoTwist; }
|
||||
virtual float getFinalLeanSideways() const { return _leanSideways; }
|
||||
virtual float getFinalLeanForward() const { return _leanForward; }
|
||||
|
||||
|
||||
void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; }
|
||||
void setLeanForward(float leanForward) { _leanForward = leanForward; }
|
||||
void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; }
|
||||
|
||||
|
||||
friend class AvatarData;
|
||||
|
||||
|
||||
QJsonObject toJson() const;
|
||||
void fromJson(const QJsonObject& json);
|
||||
|
||||
|
@ -106,9 +103,8 @@ protected:
|
|||
float _browAudioLift;
|
||||
float _audioAverageLoudness;
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
float _pupilDilation;
|
||||
AvatarData* _owningAvatar;
|
||||
|
||||
|
||||
private:
|
||||
// privatize copy ctor and assignment operator so copies of this object cannot be made
|
||||
HeadData(const HeadData&);
|
||||
|
|
|
@ -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() {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <MatrixStack.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ enum class RenderItemStatusIcon {
|
|||
SIMULATION_OWNER = 3,
|
||||
HAS_ACTIONS = 4,
|
||||
OTHER_SIMULATION_OWNER = 5,
|
||||
CLIENT_ONLY = 6,
|
||||
NONE = 255
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue