mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 04:57:23 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi
Conflicts: interface/src/Application.cpp
This commit is contained in:
commit
5b366fe714
52 changed files with 1375 additions and 142 deletions
|
@ -67,9 +67,20 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
||||||
if (argumentIndex != -1) {
|
if (argumentIndex != -1) {
|
||||||
assignmentPool = argumentList[argumentIndex + 1];
|
assignmentPool = argumentList[argumentIndex + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup our _requestAssignment member variable from the passed arguments
|
// setup our _requestAssignment member variable from the passed arguments
|
||||||
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool);
|
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool);
|
||||||
|
|
||||||
|
// check if we were passed a wallet UUID on the command line
|
||||||
|
// this would represent where the user running AC wants funds sent to
|
||||||
|
|
||||||
|
const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "--wallet";
|
||||||
|
if ((argumentIndex = argumentList.indexOf(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) != -1) {
|
||||||
|
QUuid walletUUID = QString(argumentList[argumentIndex + 1]);
|
||||||
|
qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
|
||||||
|
_requestAssignment.setWalletUUID(walletUUID);
|
||||||
|
}
|
||||||
|
|
||||||
// create a NodeList as an unassigned client
|
// create a NodeList as an unassigned client
|
||||||
NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
|
NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
<th>Public</th>
|
<th>Public</th>
|
||||||
<th>Local</th>
|
<th>Local</th>
|
||||||
<th>Uptime (s)</th>
|
<th>Uptime (s)</th>
|
||||||
|
<th>Pending Credits</th>
|
||||||
<th>Kill?</th>
|
<th>Kill?</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
@ -42,6 +42,8 @@ $(document).ready(function(){
|
||||||
var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000;
|
var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000;
|
||||||
nodesTableBody += "<td>" + uptimeSeconds.toLocaleString() + "</td>";
|
nodesTableBody += "<td>" + uptimeSeconds.toLocaleString() + "</td>";
|
||||||
|
|
||||||
|
nodesTableBody += "<td>" + (typeof data.pending_credits == 'number' ? data.pending_credits.toLocaleString() : 'N/A') + "</td>";
|
||||||
|
|
||||||
nodesTableBody += "<td><span class='glyphicon glyphicon-remove' data-uuid=" + data.uuid + "></span></td>";
|
nodesTableBody += "<td><span class='glyphicon glyphicon-remove' data-uuid=" + data.uuid + "></span></td>";
|
||||||
nodesTableBody += "</tr>";
|
nodesTableBody += "</tr>";
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
_httpsManager(NULL),
|
_httpsManager(NULL),
|
||||||
_allAssignments(),
|
_allAssignments(),
|
||||||
_unfulfilledAssignments(),
|
_unfulfilledAssignments(),
|
||||||
|
_pendingAssignedNodes(),
|
||||||
_isUsingDTLS(false),
|
_isUsingDTLS(false),
|
||||||
_oauthProviderURL(),
|
_oauthProviderURL(),
|
||||||
_oauthClientID(),
|
_oauthClientID(),
|
||||||
|
@ -49,13 +50,14 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
|
|
||||||
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
||||||
|
|
||||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
_networkAccessManager = new QNetworkAccessManager(this);
|
||||||
// we either read a certificate and private key or were not passed one, good to load assignments
|
|
||||||
// and set up the node list
|
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
||||||
|
// we either read a certificate and private key or were not passed one
|
||||||
|
// and completed login or did not need to
|
||||||
|
|
||||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
qDebug() << "Setting up LimitedNodeList and assignments.";
|
||||||
setupNodeListAndAssignments();
|
setupNodeListAndAssignments();
|
||||||
|
|
||||||
_networkAccessManager = new QNetworkAccessManager(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +131,7 @@ bool DomainServer::optionallySetupOAuth() {
|
||||||
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
||||||
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
|
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
|
||||||
|
|
||||||
if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) {
|
if (!_oauthClientID.isEmpty()) {
|
||||||
if (_oauthProviderURL.isEmpty()
|
if (_oauthProviderURL.isEmpty()
|
||||||
|| _hostname.isEmpty()
|
|| _hostname.isEmpty()
|
||||||
|| _oauthClientID.isEmpty()
|
|| _oauthClientID.isEmpty()
|
||||||
|
@ -187,6 +189,65 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
addStaticAssignmentsToQueue();
|
addStaticAssignmentsToQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DomainServer::optionallySetupAssignmentPayment() {
|
||||||
|
// check if we have a username and password set via env
|
||||||
|
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
||||||
|
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
|
||||||
|
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
|
||||||
|
|
||||||
|
if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION)) {
|
||||||
|
if (!_oauthProviderURL.isEmpty()) {
|
||||||
|
|
||||||
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
|
accountManager.setAuthURL(_oauthProviderURL);
|
||||||
|
|
||||||
|
if (!accountManager.hasValidAccessToken()) {
|
||||||
|
// we don't have a valid access token so we need to get one
|
||||||
|
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
|
||||||
|
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
|
||||||
|
|
||||||
|
if (!username.isEmpty() && !password.isEmpty()) {
|
||||||
|
accountManager.requestAccessToken(username, password);
|
||||||
|
|
||||||
|
// connect to loginFailed signal from AccountManager so we can quit if that is the case
|
||||||
|
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Missing access-token or username and password combination. domain-server will now quit.";
|
||||||
|
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume that the fact we are authing against HF data server means we will pay for assignments
|
||||||
|
// setup a timer to send transactions to pay assigned nodes every 30 seconds
|
||||||
|
QTimer* creditSetupTimer = new QTimer(this);
|
||||||
|
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
|
||||||
|
|
||||||
|
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
|
||||||
|
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
|
||||||
|
|
||||||
|
QTimer* nodePaymentTimer = new QTimer(this);
|
||||||
|
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
|
||||||
|
|
||||||
|
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
|
||||||
|
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qDebug() << "Missing OAuth provider URL, but assigned node payment was enabled. domain-server will now quit.";
|
||||||
|
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::loginFailed() {
|
||||||
|
qDebug() << "Login to data server has failed. domain-server will now quit";
|
||||||
|
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
|
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
|
||||||
// check for configs from the command line, these take precedence
|
// check for configs from the command line, these take precedence
|
||||||
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
||||||
|
@ -339,10 +400,23 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
QUuid packetUUID = uuidFromPacketHeader(packet);
|
QUuid packetUUID = uuidFromPacketHeader(packet);
|
||||||
|
|
||||||
// check if this connect request matches an assignment in the queue
|
// check if this connect request matches an assignment in the queue
|
||||||
bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
|
bool isAssignment = _pendingAssignedNodes.contains(packetUUID);
|
||||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||||
if (isFulfilledOrUnfulfilledAssignment) {
|
PendingAssignedNodeData* pendingAssigneeData = NULL;
|
||||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
|
|
||||||
|
if (isAssignment) {
|
||||||
|
pendingAssigneeData = _pendingAssignedNodes.take(packetUUID);
|
||||||
|
|
||||||
|
if (pendingAssigneeData) {
|
||||||
|
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType);
|
||||||
|
|
||||||
|
if (matchingQueuedAssignment) {
|
||||||
|
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID)
|
||||||
|
<< "matches unfulfilled assignment"
|
||||||
|
<< uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||||
|
@ -371,8 +445,8 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||||
|| (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) {
|
|| (isAssignment && matchingQueuedAssignment)) {
|
||||||
// this was either not a static assignment or it was and we had a matching one in the queue
|
// this was either not a static assignment or it was and we had a matching one in the queue
|
||||||
|
|
||||||
// create a new session UUID for this node
|
// create a new session UUID for this node
|
||||||
|
@ -384,8 +458,12 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
// if this was a static assignment set the UUID, set the sendingSockAddr
|
// if this was a static assignment set the UUID, set the sendingSockAddr
|
||||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||||
|
|
||||||
if (isFulfilledOrUnfulfilledAssignment) {
|
if (isAssignment) {
|
||||||
nodeData->setAssignmentUUID(packetUUID);
|
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||||
|
nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID());
|
||||||
|
|
||||||
|
// now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
|
||||||
|
delete pendingAssigneeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeData->setSendingSockAddr(senderSockAddr);
|
nodeData->setSendingSockAddr(senderSockAddr);
|
||||||
|
@ -564,14 +642,21 @@ void DomainServer::readAvailableDatagrams() {
|
||||||
Assignment requestAssignment(receivedPacket);
|
Assignment requestAssignment(receivedPacket);
|
||||||
|
|
||||||
// Suppress these for Assignment::AgentType to once per 5 seconds
|
// Suppress these for Assignment::AgentType to once per 5 seconds
|
||||||
static quint64 lastNoisyMessage = usecTimestampNow();
|
static QElapsedTimer noisyMessageTimer;
|
||||||
quint64 timeNow = usecTimestampNow();
|
static bool wasNoisyTimerStarted = false;
|
||||||
const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND;
|
|
||||||
bool noisyMessage = false;
|
if (!wasNoisyTimerStarted) {
|
||||||
if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
|
noisyMessageTimer.start();
|
||||||
|
wasNoisyTimerStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000;
|
||||||
|
|
||||||
|
if (requestAssignment.getType() != Assignment::AgentType
|
||||||
|
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
|
||||||
qDebug() << "Received a request for assignment type" << requestAssignment.getType()
|
qDebug() << "Received a request for assignment type" << requestAssignment.getType()
|
||||||
<< "from" << senderSockAddr;
|
<< "from" << senderSockAddr;
|
||||||
noisyMessage = true;
|
noisyMessageTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
|
SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
|
||||||
|
@ -582,23 +667,29 @@ void DomainServer::readAvailableDatagrams() {
|
||||||
// give this assignment out, either the type matches or the requestor said they will take any
|
// give this assignment out, either the type matches or the requestor said they will take any
|
||||||
assignmentPacket.resize(numAssignmentPacketHeaderBytes);
|
assignmentPacket.resize(numAssignmentPacketHeaderBytes);
|
||||||
|
|
||||||
|
// setup a copy of this assignment that will have a unique UUID, for packaging purposes
|
||||||
|
Assignment uniqueAssignment(*assignmentToDeploy.data());
|
||||||
|
uniqueAssignment.setUUID(QUuid::createUuid());
|
||||||
|
|
||||||
QDataStream assignmentStream(&assignmentPacket, QIODevice::Append);
|
QDataStream assignmentStream(&assignmentPacket, QIODevice::Append);
|
||||||
|
|
||||||
assignmentStream << *assignmentToDeploy.data();
|
assignmentStream << uniqueAssignment;
|
||||||
|
|
||||||
nodeList->getNodeSocket().writeDatagram(assignmentPacket,
|
nodeList->getNodeSocket().writeDatagram(assignmentPacket,
|
||||||
senderSockAddr.getAddress(), senderSockAddr.getPort());
|
senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||||
|
|
||||||
|
// add the information for that deployed assignment to the hash of pending assigned nodes
|
||||||
|
PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(),
|
||||||
|
requestAssignment.getWalletUUID());
|
||||||
|
_pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData);
|
||||||
} else {
|
} else {
|
||||||
if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
|
if (requestAssignment.getType() != Assignment::AgentType
|
||||||
|
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
|
||||||
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
|
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
|
||||||
<< "from" << senderSockAddr;
|
<< "from" << senderSockAddr;
|
||||||
noisyMessage = true;
|
noisyMessageTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noisyMessage) {
|
|
||||||
lastNoisyMessage = timeNow;
|
|
||||||
}
|
|
||||||
} else if (!_isUsingDTLS) {
|
} else if (!_isUsingDTLS) {
|
||||||
// not using DTLS, process datagram normally
|
// not using DTLS, process datagram normally
|
||||||
processDatagram(receivedPacket, senderSockAddr);
|
processDatagram(receivedPacket, senderSockAddr);
|
||||||
|
@ -618,6 +709,97 @@ void DomainServer::readAvailableDatagrams() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::setupPendingAssignmentCredits() {
|
||||||
|
// enumerate the NodeList to find the assigned nodes
|
||||||
|
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
|
||||||
|
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||||
|
|
||||||
|
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
|
||||||
|
// check if we have a non-finalized transaction for this node to add this amount to
|
||||||
|
TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
|
||||||
|
WalletTransaction* existingTransaction = NULL;
|
||||||
|
|
||||||
|
while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
|
||||||
|
if (!i.value()->isFinalized()) {
|
||||||
|
existingTransaction = i.value();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed();
|
||||||
|
nodeData->getPaymentIntervalTimer().restart();
|
||||||
|
|
||||||
|
const float CREDITS_PER_HOUR = 3;
|
||||||
|
const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000);
|
||||||
|
|
||||||
|
float pendingCredits = elapsedMsecsSinceLastPayment * CREDITS_PER_MSEC;
|
||||||
|
|
||||||
|
if (existingTransaction) {
|
||||||
|
existingTransaction->incrementAmount(pendingCredits);
|
||||||
|
} else {
|
||||||
|
// create a fresh transaction to pay this node, there is no transaction to append to
|
||||||
|
WalletTransaction* freshTransaction = new WalletTransaction(nodeData->getWalletUUID(), pendingCredits);
|
||||||
|
_pendingAssignmentCredits.insert(nodeData->getWalletUUID(), freshTransaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::sendPendingTransactionsToServer() {
|
||||||
|
|
||||||
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
|
|
||||||
|
if (accountManager.hasValidAccessToken()) {
|
||||||
|
|
||||||
|
// enumerate the pending transactions and send them to the server to complete payment
|
||||||
|
TransactionHash::iterator i = _pendingAssignmentCredits.begin();
|
||||||
|
|
||||||
|
JSONCallbackParameters transactionCallbackParams;
|
||||||
|
|
||||||
|
transactionCallbackParams.jsonCallbackReceiver = this;
|
||||||
|
transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback";
|
||||||
|
|
||||||
|
while (i != _pendingAssignmentCredits.end()) {
|
||||||
|
accountManager.authenticatedRequest("api/v1/transactions", QNetworkAccessManager::PostOperation,
|
||||||
|
transactionCallbackParams, i.value()->postJson().toJson());
|
||||||
|
|
||||||
|
// set this transaction to finalized so we don't add additional credits to it
|
||||||
|
i.value()->setIsFinalized(true);
|
||||||
|
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DomainServer::transactionJSONCallback(const QJsonObject& data) {
|
||||||
|
// check if this was successful - if so we can remove it from our list of pending
|
||||||
|
if (data.value("status").toString() == "success") {
|
||||||
|
// create a dummy wallet transaction to unpack the JSON to
|
||||||
|
WalletTransaction dummyTransaction;
|
||||||
|
dummyTransaction.loadFromJson(data);
|
||||||
|
|
||||||
|
TransactionHash::iterator i = _pendingAssignmentCredits.find(dummyTransaction.getDestinationUUID());
|
||||||
|
|
||||||
|
while (i != _pendingAssignmentCredits.end() && i.key() == dummyTransaction.getDestinationUUID()) {
|
||||||
|
if (i.value()->getUUID() == dummyTransaction.getUUID()) {
|
||||||
|
// we have a match - we can remove this from the hash of pending credits
|
||||||
|
// and delete it for clean up
|
||||||
|
|
||||||
|
WalletTransaction* matchingTransaction = i.value();
|
||||||
|
_pendingAssignmentCredits.erase(i);
|
||||||
|
delete matchingTransaction;
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
|
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
|
||||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||||
|
|
||||||
|
@ -667,6 +849,7 @@ const char JSON_KEY_TYPE[] = "type";
|
||||||
const char JSON_KEY_PUBLIC_SOCKET[] = "public";
|
const char JSON_KEY_PUBLIC_SOCKET[] = "public";
|
||||||
const char JSON_KEY_LOCAL_SOCKET[] = "local";
|
const char JSON_KEY_LOCAL_SOCKET[] = "local";
|
||||||
const char JSON_KEY_POOL[] = "pool";
|
const char JSON_KEY_POOL[] = "pool";
|
||||||
|
const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
|
||||||
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
|
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
|
||||||
|
|
||||||
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
||||||
|
@ -695,6 +878,18 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
||||||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||||
if (matchingAssignment) {
|
if (matchingAssignment) {
|
||||||
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
|
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
|
||||||
|
|
||||||
|
if (!nodeData->getWalletUUID().isNull()) {
|
||||||
|
TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
|
||||||
|
double pendingCreditAmount = 0;
|
||||||
|
|
||||||
|
while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
|
||||||
|
pendingCreditAmount += i.value()->getAmount();
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeJson[JSON_KEY_PENDING_CREDITS] = pendingCreditAmount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeJson;
|
return nodeJson;
|
||||||
|
@ -764,6 +959,24 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
||||||
|
|
||||||
// we've processed this request
|
// we've processed this request
|
||||||
|
return true;
|
||||||
|
} else if (url.path() == "/transactions.json") {
|
||||||
|
// enumerate our pending transactions and display them in an array
|
||||||
|
QJsonObject rootObject;
|
||||||
|
QJsonArray transactionArray;
|
||||||
|
|
||||||
|
TransactionHash::iterator i = _pendingAssignmentCredits.begin();
|
||||||
|
while (i != _pendingAssignmentCredits.end()) {
|
||||||
|
transactionArray.push_back(i.value()->toJson());
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootObject["pending_transactions"] = transactionArray;
|
||||||
|
|
||||||
|
// print out the created JSON
|
||||||
|
QJsonDocument transactionsDocument(rootObject);
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, transactionsDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (url.path() == QString("%1.json").arg(URI_NODES)) {
|
} else if (url.path() == QString("%1.json").arg(URI_NODES)) {
|
||||||
// setup the JSON
|
// setup the JSON
|
||||||
|
@ -1062,11 +1275,15 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
|
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& assignmentUUID, NodeType_t nodeType) {
|
||||||
QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
|
QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
|
||||||
|
|
||||||
while (i != _unfulfilledAssignments.end()) {
|
while (i != _unfulfilledAssignments.end()) {
|
||||||
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
|
if (i->data()->getType() == Assignment::typeForNodeType(nodeType)
|
||||||
|
&& i->data()->getUUID() == assignmentUUID) {
|
||||||
|
// we have an unfulfilled assignment to return
|
||||||
|
|
||||||
|
// return the matching assignment
|
||||||
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
|
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
|
||||||
} else {
|
} else {
|
||||||
++i;
|
++i;
|
||||||
|
|
|
@ -24,7 +24,12 @@
|
||||||
#include <HTTPSConnection.h>
|
#include <HTTPSConnection.h>
|
||||||
#include <LimitedNodeList.h>
|
#include <LimitedNodeList.h>
|
||||||
|
|
||||||
|
#include "WalletTransaction.h"
|
||||||
|
|
||||||
|
#include "PendingAssignedNodeData.h"
|
||||||
|
|
||||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||||
|
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;
|
||||||
|
|
||||||
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -42,13 +47,18 @@ public slots:
|
||||||
/// Called by NodeList to inform us a node has been killed
|
/// Called by NodeList to inform us a node has been killed
|
||||||
void nodeKilled(SharedNodePointer node);
|
void nodeKilled(SharedNodePointer node);
|
||||||
|
|
||||||
private slots:
|
void transactionJSONCallback(const QJsonObject& data);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void loginFailed();
|
||||||
void readAvailableDatagrams();
|
void readAvailableDatagrams();
|
||||||
|
void setupPendingAssignmentCredits();
|
||||||
|
void sendPendingTransactionsToServer();
|
||||||
private:
|
private:
|
||||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||||
bool optionallySetupOAuth();
|
bool optionallySetupOAuth();
|
||||||
bool optionallyReadX509KeyAndCertificate();
|
bool optionallyReadX509KeyAndCertificate();
|
||||||
|
bool optionallySetupAssignmentPayment();
|
||||||
|
|
||||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||||
|
|
||||||
|
@ -85,6 +95,8 @@ private:
|
||||||
|
|
||||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||||
|
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||||
|
TransactionHash _pendingAssignmentCredits;
|
||||||
|
|
||||||
QVariantMap _argumentVariantMap;
|
QVariantMap _argumentVariantMap;
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,13 @@
|
||||||
DomainServerNodeData::DomainServerNodeData() :
|
DomainServerNodeData::DomainServerNodeData() :
|
||||||
_sessionSecretHash(),
|
_sessionSecretHash(),
|
||||||
_assignmentUUID(),
|
_assignmentUUID(),
|
||||||
|
_walletUUID(),
|
||||||
|
_paymentIntervalTimer(),
|
||||||
_statsJSONObject(),
|
_statsJSONObject(),
|
||||||
_sendingSockAddr(),
|
_sendingSockAddr(),
|
||||||
_isAuthenticated(true)
|
_isAuthenticated(true)
|
||||||
{
|
{
|
||||||
|
_paymentIntervalTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) {
|
void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) {
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#ifndef hifi_DomainServerNodeData_h
|
#ifndef hifi_DomainServerNodeData_h
|
||||||
#define hifi_DomainServerNodeData_h
|
#define hifi_DomainServerNodeData_h
|
||||||
|
|
||||||
|
|
||||||
|
#include <QtCore/QElapsedTimer>
|
||||||
#include <QtCore/QHash>
|
#include <QtCore/QHash>
|
||||||
#include <QtCore/QUuid>
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
|
@ -30,6 +32,11 @@ public:
|
||||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||||
|
|
||||||
|
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||||
|
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||||
|
|
||||||
|
QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
|
||||||
|
|
||||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||||
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
||||||
|
|
||||||
|
@ -42,6 +49,8 @@ private:
|
||||||
|
|
||||||
QHash<QUuid, QUuid> _sessionSecretHash;
|
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||||
QUuid _assignmentUUID;
|
QUuid _assignmentUUID;
|
||||||
|
QUuid _walletUUID;
|
||||||
|
QElapsedTimer _paymentIntervalTimer;
|
||||||
QJsonObject _statsJSONObject;
|
QJsonObject _statsJSONObject;
|
||||||
HifiSockAddr _sendingSockAddr;
|
HifiSockAddr _sendingSockAddr;
|
||||||
bool _isAuthenticated;
|
bool _isAuthenticated;
|
||||||
|
|
19
domain-server/src/PendingAssignedNodeData.cpp
Normal file
19
domain-server/src/PendingAssignedNodeData.cpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// PendingAssignedNodeData.cpp
|
||||||
|
// domain-server/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 2014-05-20.
|
||||||
|
// 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 "PendingAssignedNodeData.h"
|
||||||
|
|
||||||
|
PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID) :
|
||||||
|
_assignmentUUID(assignmentUUID),
|
||||||
|
_walletUUID(walletUUID)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
33
domain-server/src/PendingAssignedNodeData.h
Normal file
33
domain-server/src/PendingAssignedNodeData.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// PendingAssignedNodeData.h
|
||||||
|
// domain-server/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 2014-05-20.
|
||||||
|
// 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_PendingAssignedNodeData_h
|
||||||
|
#define hifi_PendingAssignedNodeData_h
|
||||||
|
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
|
class PendingAssignedNodeData : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID);
|
||||||
|
|
||||||
|
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||||
|
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||||
|
|
||||||
|
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||||
|
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||||
|
private:
|
||||||
|
QUuid _assignmentUUID;
|
||||||
|
QUuid _walletUUID;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_PendingAssignedNodeData_h
|
67
domain-server/src/WalletTransaction.cpp
Normal file
67
domain-server/src/WalletTransaction.cpp
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
//
|
||||||
|
// WalletTransaction.cpp
|
||||||
|
// domain-server/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 2014-05-20.
|
||||||
|
// 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 <QtCore/QJsonObject>
|
||||||
|
|
||||||
|
#include <UUID.h>
|
||||||
|
|
||||||
|
#include "WalletTransaction.h"
|
||||||
|
|
||||||
|
WalletTransaction::WalletTransaction() :
|
||||||
|
_uuid(),
|
||||||
|
_destinationUUID(),
|
||||||
|
_amount(),
|
||||||
|
_isFinalized(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount) :
|
||||||
|
_uuid(QUuid::createUuid()),
|
||||||
|
_destinationUUID(destinationUUID),
|
||||||
|
_amount(amount),
|
||||||
|
_isFinalized(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString TRANSACTION_ID_KEY = "id";
|
||||||
|
const QString TRANSACTION_DESTINATION_WALLET_ID_KEY = "destination_wallet_id";
|
||||||
|
const QString TRANSACTION_AMOUNT_KEY = "amount";
|
||||||
|
|
||||||
|
const QString ROOT_OBJECT_TRANSACTION_KEY = "transaction";
|
||||||
|
|
||||||
|
QJsonDocument WalletTransaction::postJson() {
|
||||||
|
QJsonObject rootObject;
|
||||||
|
|
||||||
|
rootObject.insert(ROOT_OBJECT_TRANSACTION_KEY, toJson());
|
||||||
|
|
||||||
|
return QJsonDocument(rootObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject WalletTransaction::toJson() {
|
||||||
|
QJsonObject transactionObject;
|
||||||
|
|
||||||
|
transactionObject.insert(TRANSACTION_ID_KEY, uuidStringWithoutCurlyBraces(_uuid));
|
||||||
|
transactionObject.insert(TRANSACTION_DESTINATION_WALLET_ID_KEY, uuidStringWithoutCurlyBraces(_destinationUUID));
|
||||||
|
transactionObject.insert(TRANSACTION_AMOUNT_KEY, _amount);
|
||||||
|
|
||||||
|
return transactionObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WalletTransaction::loadFromJson(const QJsonObject& jsonObject) {
|
||||||
|
// pull the destination wallet and ID of the transaction to match it
|
||||||
|
QJsonObject transactionObject = jsonObject.value("data").toObject().value(ROOT_OBJECT_TRANSACTION_KEY).toObject();
|
||||||
|
|
||||||
|
_uuid = QUuid(transactionObject.value(TRANSACTION_ID_KEY).toString());
|
||||||
|
_destinationUUID = QUuid(transactionObject.value(TRANSACTION_DESTINATION_WALLET_ID_KEY).toString());
|
||||||
|
_amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toDouble();
|
||||||
|
}
|
46
domain-server/src/WalletTransaction.h
Normal file
46
domain-server/src/WalletTransaction.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// WalletTransaction.h
|
||||||
|
// domain-server/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 2014-05-20.
|
||||||
|
// 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_WalletTransaction_h
|
||||||
|
#define hifi_WalletTransaction_h
|
||||||
|
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
|
class WalletTransaction : public QObject {
|
||||||
|
public:
|
||||||
|
WalletTransaction();
|
||||||
|
WalletTransaction(const QUuid& destinationUUID, double amount);
|
||||||
|
|
||||||
|
const QUuid& getUUID() const { return _uuid; }
|
||||||
|
|
||||||
|
void setDestinationUUID(const QUuid& destinationUUID) { _destinationUUID = destinationUUID; }
|
||||||
|
const QUuid& getDestinationUUID() const { return _destinationUUID; }
|
||||||
|
|
||||||
|
double getAmount() const { return _amount; }
|
||||||
|
void setAmount(double amount) { _amount = amount; }
|
||||||
|
void incrementAmount(double increment) { _amount += increment; }
|
||||||
|
|
||||||
|
bool isFinalized() const { return _isFinalized; }
|
||||||
|
void setIsFinalized(bool isFinalized) { _isFinalized = isFinalized; }
|
||||||
|
|
||||||
|
QJsonDocument postJson();
|
||||||
|
QJsonObject toJson();
|
||||||
|
void loadFromJson(const QJsonObject& jsonObject);
|
||||||
|
private:
|
||||||
|
QUuid _uuid;
|
||||||
|
QUuid _destinationUUID;
|
||||||
|
double _amount;
|
||||||
|
bool _isFinalized;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_WalletTransaction_h
|
|
@ -630,7 +630,7 @@ var trackAsDelete = false;
|
||||||
var trackAsRecolor = false;
|
var trackAsRecolor = false;
|
||||||
var trackAsEyedropper = false;
|
var trackAsEyedropper = false;
|
||||||
|
|
||||||
var voxelToolSelected = true;
|
var voxelToolSelected = false;
|
||||||
var recolorToolSelected = false;
|
var recolorToolSelected = false;
|
||||||
var eyedropperToolSelected = false;
|
var eyedropperToolSelected = false;
|
||||||
var pasteMode = false;
|
var pasteMode = false;
|
||||||
|
@ -848,7 +848,7 @@ function showPreviewLines() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPreviewGuides() {
|
function showPreviewGuides() {
|
||||||
if (editToolsOn && !isImporting) {
|
if (editToolsOn && !isImporting && (voxelToolSelected || recolorToolSelected || eyedropperToolSelected)) {
|
||||||
if (previewAsVoxel) {
|
if (previewAsVoxel) {
|
||||||
showPreviewVoxel();
|
showPreviewVoxel();
|
||||||
|
|
||||||
|
@ -964,7 +964,7 @@ function mousePressEvent(event) {
|
||||||
|
|
||||||
if (clickedOverlay == voxelTool) {
|
if (clickedOverlay == voxelTool) {
|
||||||
modeSwitchSound.play(0);
|
modeSwitchSound.play(0);
|
||||||
voxelToolSelected = true;
|
voxelToolSelected = !voxelToolSelected;
|
||||||
recolorToolSelected = false;
|
recolorToolSelected = false;
|
||||||
eyedropperToolSelected = false;
|
eyedropperToolSelected = false;
|
||||||
moveTools();
|
moveTools();
|
||||||
|
@ -972,7 +972,7 @@ function mousePressEvent(event) {
|
||||||
} else if (clickedOverlay == recolorTool) {
|
} else if (clickedOverlay == recolorTool) {
|
||||||
modeSwitchSound.play(1);
|
modeSwitchSound.play(1);
|
||||||
voxelToolSelected = false;
|
voxelToolSelected = false;
|
||||||
recolorToolSelected = true;
|
recolorToolSelected = !recolorToolSelected;
|
||||||
eyedropperToolSelected = false;
|
eyedropperToolSelected = false;
|
||||||
moveTools();
|
moveTools();
|
||||||
clickedOnSomething = true;
|
clickedOnSomething = true;
|
||||||
|
@ -980,7 +980,7 @@ function mousePressEvent(event) {
|
||||||
modeSwitchSound.play(2);
|
modeSwitchSound.play(2);
|
||||||
voxelToolSelected = false;
|
voxelToolSelected = false;
|
||||||
recolorToolSelected = false;
|
recolorToolSelected = false;
|
||||||
eyedropperToolSelected = true;
|
eyedropperToolSelected = !eyedropperToolSelected;
|
||||||
moveTools();
|
moveTools();
|
||||||
clickedOnSomething = true;
|
clickedOnSomething = true;
|
||||||
} else if (scaleSelector.clicked(event.x, event.y)) {
|
} else if (scaleSelector.clicked(event.x, event.y)) {
|
||||||
|
@ -1000,7 +1000,7 @@ function mousePressEvent(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clickedOnSomething || isImporting) {
|
if (clickedOnSomething || isImporting || (!voxelToolSelected && !recolorToolSelected && !eyedropperToolSelected)) {
|
||||||
return; // no further processing
|
return; // no further processing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1344,7 +1344,7 @@ function moveTools() {
|
||||||
recolorToolOffset = 2;
|
recolorToolOffset = 2;
|
||||||
} else if (eyedropperToolSelected) {
|
} else if (eyedropperToolSelected) {
|
||||||
eyedropperToolOffset = 2;
|
eyedropperToolOffset = 2;
|
||||||
} else {
|
} else if (voxelToolSelected) {
|
||||||
if (pasteMode) {
|
if (pasteMode) {
|
||||||
voxelToolColor = pasteModeColor;
|
voxelToolColor = pasteModeColor;
|
||||||
}
|
}
|
||||||
|
|
153
examples/sit.js
Normal file
153
examples/sit.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
//
|
||||||
|
// sit.js
|
||||||
|
// examples
|
||||||
|
//
|
||||||
|
// Created by Mika Impola on February 8, 2014
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
var buttonImageUrl = "https://worklist-prod.s3.amazonaws.com/attachment/0aca88e1-9bd8-5c1d.svg";
|
||||||
|
|
||||||
|
var windowDimensions = Controller.getViewportDimensions();
|
||||||
|
|
||||||
|
var buttonWidth = 37;
|
||||||
|
var buttonHeight = 46;
|
||||||
|
var buttonPadding = 10;
|
||||||
|
|
||||||
|
var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth;
|
||||||
|
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ;
|
||||||
|
|
||||||
|
var sitDownButton = Overlays.addOverlay("image", {
|
||||||
|
x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
|
||||||
|
subImage: { x: 0, y: buttonHeight, width: buttonWidth, height: buttonHeight},
|
||||||
|
imageURL: buttonImageUrl,
|
||||||
|
visible: true,
|
||||||
|
alpha: 1.0
|
||||||
|
});
|
||||||
|
var standUpButton = Overlays.addOverlay("image", {
|
||||||
|
x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
|
||||||
|
subImage: { x: buttonWidth, y: buttonHeight, width: buttonWidth, height: buttonHeight},
|
||||||
|
imageURL: buttonImageUrl,
|
||||||
|
visible: false,
|
||||||
|
alpha: 1.0
|
||||||
|
});
|
||||||
|
|
||||||
|
var passedTime = 0.0;
|
||||||
|
var startPosition = null;
|
||||||
|
var animationLenght = 2.0;
|
||||||
|
|
||||||
|
// This is the pose we would like to end up
|
||||||
|
var pose = [
|
||||||
|
{joint:"RightUpLeg", rotation: {x:100.0, y:15.0, z:0.0}},
|
||||||
|
{joint:"RightLeg", rotation: {x:-130.0, y:15.0, z:0.0}},
|
||||||
|
{joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}},
|
||||||
|
{joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}},
|
||||||
|
{joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}},
|
||||||
|
{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}},
|
||||||
|
|
||||||
|
{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}},
|
||||||
|
|
||||||
|
{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}},
|
||||||
|
{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}}
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
var startPoseAndTransition = [];
|
||||||
|
|
||||||
|
function storeStartPoseAndTransition() {
|
||||||
|
for (var i = 0; i < pose.length; i++){
|
||||||
|
var startRotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(pose[i].joint));
|
||||||
|
var transitionVector = Vec3.subtract( pose[i].rotation, startRotation );
|
||||||
|
startPoseAndTransition.push({joint: pose[i].joint, start: startRotation, transition: transitionVector});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateJoints(factor){
|
||||||
|
for (var i = 0; i < startPoseAndTransition.length; i++){
|
||||||
|
var scaledTransition = Vec3.multiply(startPoseAndTransition[i].transition, factor);
|
||||||
|
var rotation = Vec3.sum(startPoseAndTransition[i].start, scaledTransition);
|
||||||
|
MyAvatar.setJointData(startPoseAndTransition[i].joint, Quat.fromVec3Degrees( rotation ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sittingDownAnimation = function(deltaTime) {
|
||||||
|
|
||||||
|
passedTime += deltaTime;
|
||||||
|
var factor = passedTime/animationLenght;
|
||||||
|
|
||||||
|
if ( passedTime <= animationLenght ) {
|
||||||
|
updateJoints(factor);
|
||||||
|
|
||||||
|
var pos = { x: startPosition.x - 0.3 * factor, y: startPosition.y - 0.5 * factor, z: startPosition.z};
|
||||||
|
MyAvatar.position = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var standingUpAnimation = function(deltaTime){
|
||||||
|
|
||||||
|
passedTime += deltaTime;
|
||||||
|
var factor = 1 - passedTime/animationLenght;
|
||||||
|
|
||||||
|
if ( passedTime <= animationLenght ) {
|
||||||
|
|
||||||
|
updateJoints(factor);
|
||||||
|
|
||||||
|
var pos = { x: startPosition.x + 0.3 * (passedTime/animationLenght), y: startPosition.y + 0.5 * (passedTime/animationLenght), z: startPosition.z};
|
||||||
|
MyAvatar.position = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.mousePressEvent.connect(function(event){
|
||||||
|
|
||||||
|
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||||
|
|
||||||
|
if (clickedOverlay == sitDownButton) {
|
||||||
|
passedTime = 0.0;
|
||||||
|
startPosition = MyAvatar.position;
|
||||||
|
storeStartPoseAndTransition();
|
||||||
|
try{
|
||||||
|
Script.update.disconnect(standingUpAnimation);
|
||||||
|
} catch(e){
|
||||||
|
// no need to handle. if it wasn't connected no harm done
|
||||||
|
}
|
||||||
|
Script.update.connect(sittingDownAnimation);
|
||||||
|
Overlays.editOverlay(sitDownButton, { visible: false });
|
||||||
|
Overlays.editOverlay(standUpButton, { visible: true });
|
||||||
|
} else if (clickedOverlay == standUpButton) {
|
||||||
|
passedTime = 0.0;
|
||||||
|
startPosition = MyAvatar.position;
|
||||||
|
try{
|
||||||
|
Script.update.disconnect(sittingDownAnimation);
|
||||||
|
} catch (e){}
|
||||||
|
Script.update.connect(standingUpAnimation);
|
||||||
|
Overlays.editOverlay(standUpButton, { visible: false });
|
||||||
|
Overlays.editOverlay(sitDownButton, { visible: true });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function update(deltaTime){
|
||||||
|
var newWindowDimensions = Controller.getViewportDimensions();
|
||||||
|
if( newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y ){
|
||||||
|
windowDimensions = newWindowDimensions;
|
||||||
|
var newX = windowDimensions.x - buttonPadding - buttonWidth;
|
||||||
|
var newY = (windowDimensions.y - buttonHeight) / 2 ;
|
||||||
|
Overlays.editOverlay( standUpButton, {x: newX, y: newY} );
|
||||||
|
Overlays.editOverlay( sitDownButton, {x: newX, y: newY} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.update.connect(update);
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
|
||||||
|
for (var i = 0; i < pose.length; i++){
|
||||||
|
MyAvatar.clearJointData(pose[i][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(sitDownButton);
|
||||||
|
Overlays.deleteOverlay(standUpButton);
|
||||||
|
});
|
|
@ -20,11 +20,14 @@ varying vec4 normal;
|
||||||
void main(void) {
|
void main(void) {
|
||||||
// compute the base color based on OpenGL lighting model
|
// compute the base color based on OpenGL lighting model
|
||||||
vec4 normalizedNormal = normalize(normal);
|
vec4 normalizedNormal = normalize(normal);
|
||||||
|
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
|
||||||
|
float facingLight = step(0.0, diffuse);
|
||||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position)));
|
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||||
|
|
||||||
// compute the specular component (sans exponent)
|
// compute the specular component (sans exponent)
|
||||||
float specular = max(0.0, dot(gl_LightSource[0].position, normalizedNormal));
|
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||||
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
|
|
|
@ -32,11 +32,14 @@ void main(void) {
|
||||||
// compute the base color based on OpenGL lighting model
|
// compute the base color based on OpenGL lighting model
|
||||||
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
|
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
|
||||||
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
||||||
|
float diffuse = dot(viewNormal, gl_LightSource[0].position);
|
||||||
|
float facingLight = step(0.0, diffuse);
|
||||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position)));
|
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||||
|
|
||||||
// compute the specular component (sans exponent)
|
// compute the specular component (sans exponent)
|
||||||
float specular = max(0.0, dot(gl_LightSource[0].position, viewNormal));
|
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||||
|
viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||||
|
|
|
@ -35,11 +35,14 @@ void main(void) {
|
||||||
// compute the base color based on OpenGL lighting model
|
// compute the base color based on OpenGL lighting model
|
||||||
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
|
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
|
||||||
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
||||||
|
float diffuse = dot(viewNormal, gl_LightSource[0].position);
|
||||||
|
float facingLight = step(0.0, diffuse);
|
||||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position)));
|
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||||
|
|
||||||
// compute the specular component (sans exponent)
|
// compute the specular component (sans exponent)
|
||||||
float specular = max(0.0, dot(gl_LightSource[0].position, viewNormal));
|
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||||
|
viewNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
||||||
|
|
|
@ -23,11 +23,14 @@ varying vec4 normal;
|
||||||
void main(void) {
|
void main(void) {
|
||||||
// compute the base color based on OpenGL lighting model
|
// compute the base color based on OpenGL lighting model
|
||||||
vec4 normalizedNormal = normalize(normal);
|
vec4 normalizedNormal = normalize(normal);
|
||||||
|
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
|
||||||
|
float facingLight = step(0.0, diffuse);
|
||||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position)));
|
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||||
|
|
||||||
// compute the specular component (sans exponent)
|
// compute the specular component (sans exponent)
|
||||||
float specular = max(0.0, dot(gl_LightSource[0].position, normalizedNormal));
|
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||||
|
normalizedNormal));
|
||||||
|
|
||||||
// modulate texture by base color and add specular contribution
|
// modulate texture by base color and add specular contribution
|
||||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
||||||
|
|
|
@ -20,12 +20,12 @@ class AbstractLoggerInterface : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {};
|
AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {}
|
||||||
inline bool extraDebugging() { return _extraDebugging; };
|
inline bool extraDebugging() { return _extraDebugging; }
|
||||||
inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; };
|
inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; }
|
||||||
|
|
||||||
virtual void addMessage(QString) = 0;
|
virtual void addMessage(QString) = 0;
|
||||||
virtual QStringList getLogData() = 0;
|
virtual QString getLogData() = 0;
|
||||||
virtual void locateLog() = 0;
|
virtual void locateLog() = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -3592,9 +3592,12 @@ void Application::loadScriptURLDialog() {
|
||||||
void Application::toggleLogDialog() {
|
void Application::toggleLogDialog() {
|
||||||
if (! _logDialog) {
|
if (! _logDialog) {
|
||||||
_logDialog = new LogDialog(_glWidget, getLogger());
|
_logDialog = new LogDialog(_glWidget, getLogger());
|
||||||
_logDialog->show();
|
}
|
||||||
|
|
||||||
|
if (_logDialog->isVisible()) {
|
||||||
|
_logDialog->hide();
|
||||||
} else {
|
} else {
|
||||||
_logDialog->close();
|
_logDialog->show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,7 @@ public:
|
||||||
|
|
||||||
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
|
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
|
||||||
GeometryCache* getGeometryCache() { return &_geometryCache; }
|
GeometryCache* getGeometryCache() { return &_geometryCache; }
|
||||||
|
AnimationCache* getAnimationCache() { return &_animationCache; }
|
||||||
TextureCache* getTextureCache() { return &_textureCache; }
|
TextureCache* getTextureCache() { return &_textureCache; }
|
||||||
GlowEffect* getGlowEffect() { return &_glowEffect; }
|
GlowEffect* getGlowEffect() { return &_glowEffect; }
|
||||||
ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
|
ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
|
||||||
|
|
|
@ -23,7 +23,7 @@ const QString LOGS_DIRECTORY = "Logs";
|
||||||
|
|
||||||
FileLogger::FileLogger(QObject* parent) :
|
FileLogger::FileLogger(QObject* parent) :
|
||||||
AbstractLoggerInterface(parent),
|
AbstractLoggerInterface(parent),
|
||||||
_logData(NULL)
|
_logData("")
|
||||||
{
|
{
|
||||||
setExtraDebugging(false);
|
setExtraDebugging(false);
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ FileLogger::FileLogger(QObject* parent) :
|
||||||
void FileLogger::addMessage(QString message) {
|
void FileLogger::addMessage(QString message) {
|
||||||
QMutexLocker locker(&_mutex);
|
QMutexLocker locker(&_mutex);
|
||||||
emit logReceived(message);
|
emit logReceived(message);
|
||||||
_logData.append(message);
|
_logData += message;
|
||||||
|
|
||||||
QFile file(_fileName);
|
QFile file(_fileName);
|
||||||
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||||
|
|
|
@ -22,11 +22,11 @@ public:
|
||||||
FileLogger(QObject* parent = NULL);
|
FileLogger(QObject* parent = NULL);
|
||||||
|
|
||||||
virtual void addMessage(QString);
|
virtual void addMessage(QString);
|
||||||
virtual QStringList getLogData() { return _logData; };
|
virtual QString getLogData() { return _logData; }
|
||||||
virtual void locateLog();
|
virtual void locateLog();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList _logData;
|
QString _logData;
|
||||||
QString _fileName;
|
QString _fileName;
|
||||||
QMutex _mutex;
|
QMutex _mutex;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "scripting/MenuScriptingInterface.h"
|
#include "scripting/MenuScriptingInterface.h"
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
|
#include "ui/AnimationsDialog.h"
|
||||||
#include "ui/AttachmentsDialog.h"
|
#include "ui/AttachmentsDialog.h"
|
||||||
#include "ui/InfoView.h"
|
#include "ui/InfoView.h"
|
||||||
#include "ui/MetavoxelEditor.h"
|
#include "ui/MetavoxelEditor.h"
|
||||||
|
@ -193,6 +194,7 @@ Menu::Menu() :
|
||||||
QAction::PreferencesRole);
|
QAction::PreferencesRole);
|
||||||
|
|
||||||
addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments()));
|
addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments()));
|
||||||
|
addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations()));
|
||||||
|
|
||||||
addDisabledActionAndSeparator(editMenu, "Physics");
|
addDisabledActionAndSeparator(editMenu, "Physics");
|
||||||
QObject* avatar = appInstance->getAvatar();
|
QObject* avatar = appInstance->getAvatar();
|
||||||
|
@ -863,6 +865,15 @@ void Menu::editAttachments() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::editAnimations() {
|
||||||
|
if (!_animationsDialog) {
|
||||||
|
_animationsDialog = new AnimationsDialog();
|
||||||
|
_animationsDialog->show();
|
||||||
|
} else {
|
||||||
|
_animationsDialog->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Menu::goToDomain(const QString newDomain) {
|
void Menu::goToDomain(const QString newDomain) {
|
||||||
if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) {
|
if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) {
|
||||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||||
|
|
|
@ -64,6 +64,7 @@ struct ViewFrustumOffset {
|
||||||
|
|
||||||
class QSettings;
|
class QSettings;
|
||||||
|
|
||||||
|
class AnimationsDialog;
|
||||||
class AttachmentsDialog;
|
class AttachmentsDialog;
|
||||||
class BandwidthDialog;
|
class BandwidthDialog;
|
||||||
class LodToolsDialog;
|
class LodToolsDialog;
|
||||||
|
@ -176,6 +177,7 @@ private slots:
|
||||||
void aboutApp();
|
void aboutApp();
|
||||||
void editPreferences();
|
void editPreferences();
|
||||||
void editAttachments();
|
void editAttachments();
|
||||||
|
void editAnimations();
|
||||||
void goToDomainDialog();
|
void goToDomainDialog();
|
||||||
void goToLocation();
|
void goToLocation();
|
||||||
void nameLocation();
|
void nameLocation();
|
||||||
|
@ -260,6 +262,7 @@ private:
|
||||||
QAction* _loginAction;
|
QAction* _loginAction;
|
||||||
QPointer<PreferencesDialog> _preferencesDialog;
|
QPointer<PreferencesDialog> _preferencesDialog;
|
||||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||||
|
QPointer<AnimationsDialog> _animationsDialog;
|
||||||
QAction* _chatAction;
|
QAction* _chatAction;
|
||||||
QString _snapshotsLocation;
|
QString _snapshotsLocation;
|
||||||
};
|
};
|
||||||
|
@ -270,6 +273,7 @@ namespace MenuOption {
|
||||||
const QString AllowOculusCameraModeChange = "Allow Oculus Camera Mode Change (Nausea)";
|
const QString AllowOculusCameraModeChange = "Allow Oculus Camera Mode Change (Nausea)";
|
||||||
const QString AlternateIK = "Alternate IK";
|
const QString AlternateIK = "Alternate IK";
|
||||||
const QString AmbientOcclusion = "Ambient Occlusion";
|
const QString AmbientOcclusion = "Ambient Occlusion";
|
||||||
|
const QString Animations = "Animations...";
|
||||||
const QString Atmosphere = "Atmosphere";
|
const QString Atmosphere = "Atmosphere";
|
||||||
const QString Attachments = "Attachments...";
|
const QString Attachments = "Attachments...";
|
||||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||||
|
|
|
@ -428,6 +428,47 @@ void MyAvatar::setGravity(const glm::vec3& gravity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimationHandlePointer MyAvatar::addAnimationHandle() {
|
||||||
|
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
|
||||||
|
handle->setLoop(true);
|
||||||
|
handle->start();
|
||||||
|
_animationHandles.append(handle);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) {
|
||||||
|
handle->stop();
|
||||||
|
_animationHandles.removeOne(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop, const QStringList& maskedJoints) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url),
|
||||||
|
Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(const QStringList&, maskedJoints));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
|
||||||
|
handle->setURL(url);
|
||||||
|
handle->setFPS(fps);
|
||||||
|
handle->setPriority(priority);
|
||||||
|
handle->setLoop(loop);
|
||||||
|
handle->setMaskedJoints(maskedJoints);
|
||||||
|
handle->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::stopAnimation(const QString& url) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
|
||||||
|
if (handle->getURL() == url) {
|
||||||
|
handle->stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::saveData(QSettings* settings) {
|
void MyAvatar::saveData(QSettings* settings) {
|
||||||
settings->beginGroup("Avatar");
|
settings->beginGroup("Avatar");
|
||||||
|
|
||||||
|
@ -466,6 +507,17 @@ void MyAvatar::saveData(QSettings* settings) {
|
||||||
}
|
}
|
||||||
settings->endArray();
|
settings->endArray();
|
||||||
|
|
||||||
|
settings->beginWriteArray("animationHandles");
|
||||||
|
for (int i = 0; i < _animationHandles.size(); i++) {
|
||||||
|
settings->setArrayIndex(i);
|
||||||
|
const AnimationHandlePointer& pointer = _animationHandles.at(i);
|
||||||
|
settings->setValue("url", pointer->getURL());
|
||||||
|
settings->setValue("fps", pointer->getFPS());
|
||||||
|
settings->setValue("priority", pointer->getPriority());
|
||||||
|
settings->setValue("maskedJoints", pointer->getMaskedJoints());
|
||||||
|
}
|
||||||
|
settings->endArray();
|
||||||
|
|
||||||
settings->setValue("displayName", _displayName);
|
settings->setValue("displayName", _displayName);
|
||||||
|
|
||||||
settings->endGroup();
|
settings->endGroup();
|
||||||
|
@ -516,6 +568,23 @@ void MyAvatar::loadData(QSettings* settings) {
|
||||||
settings->endArray();
|
settings->endArray();
|
||||||
setAttachmentData(attachmentData);
|
setAttachmentData(attachmentData);
|
||||||
|
|
||||||
|
int animationCount = settings->beginReadArray("animationHandles");
|
||||||
|
while (_animationHandles.size() > animationCount) {
|
||||||
|
_animationHandles.takeLast()->stop();
|
||||||
|
}
|
||||||
|
while (_animationHandles.size() < animationCount) {
|
||||||
|
addAnimationHandle();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < animationCount; i++) {
|
||||||
|
settings->setArrayIndex(i);
|
||||||
|
const AnimationHandlePointer& handle = _animationHandles.at(i);
|
||||||
|
handle->setURL(settings->value("url").toUrl());
|
||||||
|
handle->setFPS(loadSetting(settings, "fps", 30.0f));
|
||||||
|
handle->setPriority(loadSetting(settings, "priority", 1.0f));
|
||||||
|
handle->setMaskedJoints(settings->value("maskedJoints").toStringList());
|
||||||
|
}
|
||||||
|
settings->endArray();
|
||||||
|
|
||||||
setDisplayName(settings->value("displayName").toString());
|
setDisplayName(settings->value("displayName").toString());
|
||||||
|
|
||||||
settings->endGroup();
|
settings->endGroup();
|
||||||
|
@ -1546,3 +1615,4 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe
|
||||||
getHead()->addLeanDeltas(sideways, forward);
|
getHead()->addLeanDeltas(sideways, forward);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,17 @@ public:
|
||||||
glm::vec3 getUprightHeadPosition() const;
|
glm::vec3 getUprightHeadPosition() const;
|
||||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||||
|
|
||||||
|
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
|
||||||
|
AnimationHandlePointer addAnimationHandle();
|
||||||
|
void removeAnimationHandle(const AnimationHandlePointer& handle);
|
||||||
|
|
||||||
|
/// Allows scripts to run animations.
|
||||||
|
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f,
|
||||||
|
float priority = 1.0f, bool loop = false, const QStringList& maskedJoints = QStringList());
|
||||||
|
|
||||||
|
/// Stops an animation as identified by a URL.
|
||||||
|
Q_INVOKABLE void stopAnimation(const QString& url);
|
||||||
|
|
||||||
// get/set avatar data
|
// get/set avatar data
|
||||||
void saveData(QSettings* settings);
|
void saveData(QSettings* settings);
|
||||||
void loadData(QSettings* settings);
|
void loadData(QSettings* settings);
|
||||||
|
@ -151,6 +162,8 @@ private:
|
||||||
bool _billboardValid;
|
bool _billboardValid;
|
||||||
float _oculusYawOffset;
|
float _oculusYawOffset;
|
||||||
|
|
||||||
|
QList<AnimationHandlePointer> _animationHandles;
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
void updateOrientation(float deltaTime);
|
void updateOrientation(float deltaTime);
|
||||||
void updateMotorFromKeyboard(float deltaTime, bool walking);
|
void updateMotorFromKeyboard(float deltaTime, bool walking);
|
||||||
|
|
|
@ -404,6 +404,22 @@ QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance
|
||||||
return lod;
|
return lod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint qHash(const QWeakPointer<Animation>& animation, uint seed = 0) {
|
||||||
|
return qHash(animation.data(), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<int> NetworkGeometry::getJointMappings(const AnimationPointer& animation) {
|
||||||
|
QVector<int> mappings = _jointMappings.value(animation);
|
||||||
|
if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) {
|
||||||
|
const FBXGeometry& animationGeometry = animation->getGeometry();
|
||||||
|
for (int i = 0; i < animationGeometry.joints.size(); i++) {
|
||||||
|
mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1);
|
||||||
|
}
|
||||||
|
_jointMappings.insert(animation, mappings);
|
||||||
|
}
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
|
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
|
||||||
Resource::setLoadPriority(owner, priority);
|
Resource::setLoadPriority(owner, priority);
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
#include <FBXReader.h>
|
#include <FBXReader.h>
|
||||||
|
|
||||||
|
#include <AnimationCache.h>
|
||||||
|
|
||||||
class Model;
|
class Model;
|
||||||
class NetworkGeometry;
|
class NetworkGeometry;
|
||||||
class NetworkMesh;
|
class NetworkMesh;
|
||||||
|
@ -90,6 +92,8 @@ public:
|
||||||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
||||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||||
|
|
||||||
|
QVector<int> getJointMappings(const AnimationPointer& animation);
|
||||||
|
|
||||||
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
|
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
|
||||||
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
|
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
|
||||||
virtual void clearLoadPriority(const QPointer<QObject>& owner);
|
virtual void clearLoadPriority(const QPointer<QObject>& owner);
|
||||||
|
@ -117,6 +121,8 @@ private:
|
||||||
QVector<NetworkMesh> _meshes;
|
QVector<NetworkMesh> _meshes;
|
||||||
|
|
||||||
QWeakPointer<NetworkGeometry> _lodParent;
|
QWeakPointer<NetworkGeometry> _lodParent;
|
||||||
|
|
||||||
|
QHash<QWeakPointer<Animation>, QVector<int> > _jointMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The state associated with a single mesh part.
|
/// The state associated with a single mesh part.
|
||||||
|
|
|
@ -118,6 +118,7 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
||||||
JointState state;
|
JointState state;
|
||||||
state.translation = joint.translation;
|
state.translation = joint.translation;
|
||||||
state.rotation = joint.rotation;
|
state.rotation = joint.rotation;
|
||||||
|
state.animationDisabled = false;
|
||||||
jointStates.append(state);
|
jointStates.append(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,8 +436,12 @@ Extents Model::getMeshExtents() const {
|
||||||
return Extents();
|
return Extents();
|
||||||
}
|
}
|
||||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||||
glm::vec3 scale = _scale * _geometry->getFBXGeometry().fstScaled;
|
|
||||||
Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale };
|
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||||
|
// is captured in the offset matrix
|
||||||
|
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0));
|
||||||
|
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0));
|
||||||
|
Extents scaledExtents = { minimum * _scale, maximum * _scale };
|
||||||
return scaledExtents;
|
return scaledExtents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,9 +452,12 @@ Extents Model::getUnscaledMeshExtents() const {
|
||||||
|
|
||||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||||
|
|
||||||
// even though our caller asked for "unscaled" we need to include any fst scaling
|
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||||
float scale = _geometry->getFBXGeometry().fstScaled;
|
// is captured in the offset matrix
|
||||||
Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale };
|
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0));
|
||||||
|
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0));
|
||||||
|
Extents scaledExtents = { minimum, maximum };
|
||||||
|
|
||||||
return scaledExtents;
|
return scaledExtents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,6 +602,16 @@ QStringList Model::getJointNames() const {
|
||||||
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
|
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint qHash(const WeakAnimationHandlePointer& handle, uint seed) {
|
||||||
|
return qHash(handle.data(), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationHandlePointer Model::createAnimationHandle() {
|
||||||
|
AnimationHandlePointer handle(new AnimationHandle(this));
|
||||||
|
handle->_self = handle;
|
||||||
|
_animationHandles.insert(handle);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
void Model::clearShapes() {
|
void Model::clearShapes() {
|
||||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||||
|
@ -1006,6 +1024,11 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::simulateInternal(float deltaTime) {
|
void Model::simulateInternal(float deltaTime) {
|
||||||
|
// update animations
|
||||||
|
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||||
|
handle->simulate(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
||||||
// update the world space transforms for all joints
|
// update the world space transforms for all joints
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
|
@ -1013,9 +1036,8 @@ void Model::simulateInternal(float deltaTime) {
|
||||||
}
|
}
|
||||||
_shapesAreDirty = true;
|
_shapesAreDirty = true;
|
||||||
|
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
|
||||||
|
|
||||||
// update the attachment transforms and simulate them
|
// update the attachment transforms and simulate them
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
for (int i = 0; i < _attachments.size(); i++) {
|
for (int i = 0; i < _attachments.size(); i++) {
|
||||||
const FBXAttachment& attachment = geometry.attachments.at(i);
|
const FBXAttachment& attachment = geometry.attachments.at(i);
|
||||||
Model* model = _attachments.at(i);
|
Model* model = _attachments.at(i);
|
||||||
|
@ -1186,6 +1208,7 @@ bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fro
|
||||||
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation *
|
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation *
|
||||||
glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation :
|
glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation :
|
||||||
_geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation);
|
_geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation);
|
||||||
|
state.animationDisabled = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1218,6 +1241,7 @@ bool Model::restoreJointPosition(int jointIndex, float percent) {
|
||||||
const FBXJoint& joint = geometry.joints.at(index);
|
const FBXJoint& joint = geometry.joints.at(index);
|
||||||
state.rotation = safeMix(state.rotation, joint.rotation, percent);
|
state.rotation = safeMix(state.rotation, joint.rotation, percent);
|
||||||
state.translation = glm::mix(state.translation, joint.translation, percent);
|
state.translation = glm::mix(state.translation, joint.translation, percent);
|
||||||
|
state.animationDisabled = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1251,6 +1275,7 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons
|
||||||
glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax));
|
glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax));
|
||||||
state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation;
|
state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation;
|
||||||
state.rotation = newRotation;
|
state.rotation = newRotation;
|
||||||
|
state.animationDisabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int BALL_SUBDIVISIONS = 10;
|
const int BALL_SUBDIVISIONS = 10;
|
||||||
|
@ -1426,6 +1451,16 @@ void Model::deleteGeometry() {
|
||||||
_meshStates.clear();
|
_meshStates.clear();
|
||||||
clearShapes();
|
clearShapes();
|
||||||
|
|
||||||
|
for (QSet<WeakAnimationHandlePointer>::iterator it = _animationHandles.begin(); it != _animationHandles.end(); ) {
|
||||||
|
AnimationHandlePointer handle = it->toStrongRef();
|
||||||
|
if (handle) {
|
||||||
|
handle->_jointMappings.clear();
|
||||||
|
it++;
|
||||||
|
} else {
|
||||||
|
it = _animationHandles.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_geometry) {
|
if (_geometry) {
|
||||||
_geometry->clearLoadPriority(this);
|
_geometry->clearLoadPriority(this);
|
||||||
}
|
}
|
||||||
|
@ -1621,3 +1656,117 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
|
||||||
activeProgram->release();
|
activeProgram->release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimationHandle::setURL(const QUrl& url) {
|
||||||
|
if (_url != url) {
|
||||||
|
_animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url);
|
||||||
|
_jointMappings.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
|
||||||
|
for (QList<AnimationHandlePointer>::iterator it = handles.begin(); it != handles.end(); it++) {
|
||||||
|
if (handle->getPriority() < (*it)->getPriority()) {
|
||||||
|
handles.insert(it, handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handles.append(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationHandle::setPriority(float priority) {
|
||||||
|
if (_priority != priority) {
|
||||||
|
_priority = priority;
|
||||||
|
if (_running) {
|
||||||
|
_model->_runningAnimations.removeOne(_self);
|
||||||
|
insertSorted(_model->_runningAnimations, _self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) {
|
||||||
|
_maskedJoints = maskedJoints;
|
||||||
|
_jointMappings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationHandle::setRunning(bool running) {
|
||||||
|
if ((_running = running)) {
|
||||||
|
if (!_model->_runningAnimations.contains(_self)) {
|
||||||
|
insertSorted(_model->_runningAnimations, _self);
|
||||||
|
}
|
||||||
|
_frameIndex = 0.0f;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_model->_runningAnimations.removeOne(_self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationHandle::AnimationHandle(Model* model) :
|
||||||
|
QObject(model),
|
||||||
|
_model(model),
|
||||||
|
_fps(30.0f),
|
||||||
|
_priority(1.0f),
|
||||||
|
_loop(false),
|
||||||
|
_running(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationHandle::simulate(float deltaTime) {
|
||||||
|
_frameIndex += deltaTime * _fps;
|
||||||
|
|
||||||
|
// update the joint mappings if necessary/possible
|
||||||
|
if (_jointMappings.isEmpty()) {
|
||||||
|
if (_model->isActive()) {
|
||||||
|
_jointMappings = _model->getGeometry()->getJointMappings(_animation);
|
||||||
|
}
|
||||||
|
if (_jointMappings.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_maskedJoints.isEmpty()) {
|
||||||
|
const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry();
|
||||||
|
for (int i = 0; i < _jointMappings.size(); i++) {
|
||||||
|
int& mapping = _jointMappings[i];
|
||||||
|
if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) {
|
||||||
|
mapping = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FBXGeometry& animationGeometry = _animation->getGeometry();
|
||||||
|
if (animationGeometry.animationFrames.isEmpty()) {
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int ceilFrameIndex = (int)glm::ceil(_frameIndex);
|
||||||
|
if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) {
|
||||||
|
// passed the end; apply the last frame
|
||||||
|
const FBXAnimationFrame& frame = animationGeometry.animationFrames.last();
|
||||||
|
for (int i = 0; i < _jointMappings.size(); i++) {
|
||||||
|
int mapping = _jointMappings.at(i);
|
||||||
|
if (mapping != -1) {
|
||||||
|
Model::JointState& state = _model->_jointStates[mapping];
|
||||||
|
if (!state.animationDisabled) {
|
||||||
|
state.rotation = frame.rotations.at(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// blend between the closest two frames
|
||||||
|
const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at(
|
||||||
|
ceilFrameIndex % animationGeometry.animationFrames.size());
|
||||||
|
const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at(
|
||||||
|
(int)glm::floor(_frameIndex) % animationGeometry.animationFrames.size());
|
||||||
|
float frameFraction = glm::fract(_frameIndex);
|
||||||
|
for (int i = 0; i < _jointMappings.size(); i++) {
|
||||||
|
int mapping = _jointMappings.at(i);
|
||||||
|
if (mapping != -1) {
|
||||||
|
Model::JointState& state = _model->_jointStates[mapping];
|
||||||
|
if (!state.animationDisabled) {
|
||||||
|
state.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,18 +12,25 @@
|
||||||
#ifndef hifi_Model_h
|
#ifndef hifi_Model_h
|
||||||
#define hifi_Model_h
|
#define hifi_Model_h
|
||||||
|
|
||||||
|
#include <QBitArray>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <CapsuleShape.h>
|
#include <CapsuleShape.h>
|
||||||
|
|
||||||
|
#include <AnimationCache.h>
|
||||||
|
|
||||||
#include "GeometryCache.h"
|
#include "GeometryCache.h"
|
||||||
#include "InterfaceConfig.h"
|
#include "InterfaceConfig.h"
|
||||||
#include "ProgramObject.h"
|
#include "ProgramObject.h"
|
||||||
#include "TextureCache.h"
|
#include "TextureCache.h"
|
||||||
|
|
||||||
|
class AnimationHandle;
|
||||||
class Shape;
|
class Shape;
|
||||||
|
|
||||||
|
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
|
||||||
|
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
|
||||||
|
|
||||||
/// A generic 3D model displaying geometry loaded from a URL.
|
/// A generic 3D model displaying geometry loaded from a URL.
|
||||||
class Model : public QObject {
|
class Model : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -185,6 +192,10 @@ public:
|
||||||
|
|
||||||
QStringList getJointNames() const;
|
QStringList getJointNames() const;
|
||||||
|
|
||||||
|
AnimationHandlePointer createAnimationHandle();
|
||||||
|
|
||||||
|
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
|
||||||
|
|
||||||
void clearShapes();
|
void clearShapes();
|
||||||
void rebuildShapes();
|
void rebuildShapes();
|
||||||
void resetShapePositions();
|
void resetShapePositions();
|
||||||
|
@ -243,6 +254,7 @@ protected:
|
||||||
glm::quat rotation; // rotation relative to parent
|
glm::quat rotation; // rotation relative to parent
|
||||||
glm::mat4 transform; // rotation to world frame + translation in model frame
|
glm::mat4 transform; // rotation to world frame + translation in model frame
|
||||||
glm::quat combinedRotation; // rotation from joint local to world frame
|
glm::quat combinedRotation; // rotation from joint local to world frame
|
||||||
|
bool animationDisabled; // if true, animations do not affect this joint
|
||||||
};
|
};
|
||||||
|
|
||||||
bool _shapesAreDirty;
|
bool _shapesAreDirty;
|
||||||
|
@ -299,6 +311,8 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
friend class AnimationHandle;
|
||||||
|
|
||||||
void applyNextGeometry();
|
void applyNextGeometry();
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
void renderMeshes(float alpha, RenderMode mode, bool translucent);
|
void renderMeshes(float alpha, RenderMode mode, bool translucent);
|
||||||
|
@ -322,6 +336,10 @@ private:
|
||||||
|
|
||||||
QVector<Model*> _attachments;
|
QVector<Model*> _attachments;
|
||||||
|
|
||||||
|
QSet<WeakAnimationHandlePointer> _animationHandles;
|
||||||
|
|
||||||
|
QList<AnimationHandlePointer> _runningAnimations;
|
||||||
|
|
||||||
static ProgramObject _program;
|
static ProgramObject _program;
|
||||||
static ProgramObject _normalMapProgram;
|
static ProgramObject _normalMapProgram;
|
||||||
static ProgramObject _specularMapProgram;
|
static ProgramObject _specularMapProgram;
|
||||||
|
@ -357,4 +375,52 @@ Q_DECLARE_METATYPE(QPointer<Model>)
|
||||||
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
|
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
|
||||||
Q_DECLARE_METATYPE(QVector<glm::vec3>)
|
Q_DECLARE_METATYPE(QVector<glm::vec3>)
|
||||||
|
|
||||||
|
/// Represents a handle to a model animation.
|
||||||
|
class AnimationHandle : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
void setURL(const QUrl& url);
|
||||||
|
const QUrl& getURL() const { return _url; }
|
||||||
|
|
||||||
|
void setFPS(float fps) { _fps = fps; }
|
||||||
|
float getFPS() const { return _fps; }
|
||||||
|
|
||||||
|
void setPriority(float priority);
|
||||||
|
float getPriority() const { return _priority; }
|
||||||
|
|
||||||
|
void setLoop(bool loop) { _loop = loop; }
|
||||||
|
bool getLoop() const { return _loop; }
|
||||||
|
|
||||||
|
void setMaskedJoints(const QStringList& maskedJoints);
|
||||||
|
const QStringList& getMaskedJoints() const { return _maskedJoints; }
|
||||||
|
|
||||||
|
void setRunning(bool running);
|
||||||
|
bool isRunning() const { return _running; }
|
||||||
|
|
||||||
|
void start() { setRunning(true); }
|
||||||
|
void stop() { setRunning(false); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
friend class Model;
|
||||||
|
|
||||||
|
AnimationHandle(Model* model);
|
||||||
|
|
||||||
|
void simulate(float deltaTime);
|
||||||
|
|
||||||
|
Model* _model;
|
||||||
|
WeakAnimationHandlePointer _self;
|
||||||
|
AnimationPointer _animation;
|
||||||
|
QUrl _url;
|
||||||
|
float _fps;
|
||||||
|
float _priority;
|
||||||
|
bool _loop;
|
||||||
|
QStringList _maskedJoints;
|
||||||
|
bool _running;
|
||||||
|
QVector<int> _jointMappings;
|
||||||
|
float _frameIndex;
|
||||||
|
};
|
||||||
|
|
||||||
#endif // hifi_Model_h
|
#endif // hifi_Model_h
|
||||||
|
|
158
interface/src/ui/AnimationsDialog.cpp
Normal file
158
interface/src/ui/AnimationsDialog.cpp
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
//
|
||||||
|
// AnimationsDialog.cpp
|
||||||
|
// interface/src/ui
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 5/19/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 <QDialogButtonBox>
|
||||||
|
#include <QDoubleSpinBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "AnimationsDialog.h"
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
AnimationsDialog::AnimationsDialog() :
|
||||||
|
QDialog(Application::getInstance()->getWindow()) {
|
||||||
|
|
||||||
|
setWindowTitle("Edit Animations");
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
QScrollArea* area = new QScrollArea();
|
||||||
|
layout->addWidget(area);
|
||||||
|
area->setWidgetResizable(true);
|
||||||
|
QWidget* container = new QWidget();
|
||||||
|
container->setLayout(_animations = new QVBoxLayout());
|
||||||
|
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||||
|
area->setWidget(container);
|
||||||
|
_animations->addStretch(1);
|
||||||
|
|
||||||
|
foreach (const AnimationHandlePointer& handle, Application::getInstance()->getAvatar()->getAnimationHandles()) {
|
||||||
|
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton* newAnimation = new QPushButton("New Animation");
|
||||||
|
connect(newAnimation, SIGNAL(clicked(bool)), SLOT(addAnimation()));
|
||||||
|
layout->addWidget(newAnimation);
|
||||||
|
|
||||||
|
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
|
||||||
|
_ok = buttons->button(QDialogButtonBox::Ok);
|
||||||
|
|
||||||
|
setMinimumSize(600, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationsDialog::setVisible(bool visible) {
|
||||||
|
QDialog::setVisible(visible);
|
||||||
|
|
||||||
|
// un-default the OK button
|
||||||
|
if (visible) {
|
||||||
|
_ok->setDefault(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationsDialog::addAnimation() {
|
||||||
|
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(
|
||||||
|
this, Application::getInstance()->getAvatar()->addAnimationHandle()));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle) :
|
||||||
|
_dialog(dialog),
|
||||||
|
_handle(handle),
|
||||||
|
_applying(false) {
|
||||||
|
setFrameStyle(QFrame::StyledPanel);
|
||||||
|
|
||||||
|
QFormLayout* layout = new QFormLayout();
|
||||||
|
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
QHBoxLayout* urlBox = new QHBoxLayout();
|
||||||
|
layout->addRow("URL:", urlBox);
|
||||||
|
urlBox->addWidget(_url = new QLineEdit(handle->getURL().toString()), 1);
|
||||||
|
connect(_url, SIGNAL(returnPressed()), SLOT(updateHandle()));
|
||||||
|
QPushButton* chooseURL = new QPushButton("Choose");
|
||||||
|
urlBox->addWidget(chooseURL);
|
||||||
|
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseURL()));
|
||||||
|
|
||||||
|
layout->addRow("FPS:", _fps = new QDoubleSpinBox());
|
||||||
|
_fps->setSingleStep(0.01);
|
||||||
|
_fps->setMaximum(FLT_MAX);
|
||||||
|
_fps->setValue(handle->getFPS());
|
||||||
|
connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle()));
|
||||||
|
|
||||||
|
layout->addRow("Priority:", _priority = new QDoubleSpinBox());
|
||||||
|
_priority->setSingleStep(0.01);
|
||||||
|
_priority->setMinimum(-FLT_MAX);
|
||||||
|
_priority->setMaximum(FLT_MAX);
|
||||||
|
_priority->setValue(handle->getPriority());
|
||||||
|
connect(_priority, SIGNAL(valueChanged(double)), SLOT(updateHandle()));
|
||||||
|
|
||||||
|
QHBoxLayout* maskedJointBox = new QHBoxLayout();
|
||||||
|
layout->addRow("Masked Joints:", maskedJointBox);
|
||||||
|
maskedJointBox->addWidget(_maskedJoints = new QLineEdit(handle->getMaskedJoints().join(", ")), 1);
|
||||||
|
connect(_maskedJoints, SIGNAL(returnPressed()), SLOT(updateHandle()));
|
||||||
|
maskedJointBox->addWidget(_chooseMaskedJoints = new QPushButton("Choose"));
|
||||||
|
connect(_chooseMaskedJoints, SIGNAL(clicked(bool)), SLOT(chooseMaskedJoints()));
|
||||||
|
|
||||||
|
QPushButton* remove = new QPushButton("Delete");
|
||||||
|
layout->addRow(remove);
|
||||||
|
connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationPanel::chooseURL() {
|
||||||
|
QString directory = Application::getInstance()->lockSettings()->value("animation_directory").toString();
|
||||||
|
Application::getInstance()->unlockSettings();
|
||||||
|
QString filename = QFileDialog::getOpenFileName(this, "Choose Animation", directory, "Animation files (*.fbx)");
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Application::getInstance()->lockSettings()->setValue("animation_directory", QFileInfo(filename).path());
|
||||||
|
Application::getInstance()->unlockSettings();
|
||||||
|
_url->setText(QUrl::fromLocalFile(filename).toString());
|
||||||
|
emit _url->returnPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationPanel::chooseMaskedJoints() {
|
||||||
|
QMenu menu;
|
||||||
|
QStringList maskedJoints = _handle->getMaskedJoints();
|
||||||
|
foreach (const QString& jointName, Application::getInstance()->getAvatar()->getJointNames()) {
|
||||||
|
QAction* action = menu.addAction(jointName);
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(maskedJoints.contains(jointName));
|
||||||
|
}
|
||||||
|
QAction* action = menu.exec(_chooseMaskedJoints->mapToGlobal(QPoint(0, 0)));
|
||||||
|
if (action) {
|
||||||
|
if (action->isChecked()) {
|
||||||
|
maskedJoints.append(action->text());
|
||||||
|
} else {
|
||||||
|
maskedJoints.removeOne(action->text());
|
||||||
|
}
|
||||||
|
_handle->setMaskedJoints(maskedJoints);
|
||||||
|
_maskedJoints->setText(maskedJoints.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationPanel::updateHandle() {
|
||||||
|
_handle->setURL(_url->text());
|
||||||
|
_handle->setFPS(_fps->value());
|
||||||
|
_handle->setPriority(_priority->value());
|
||||||
|
_handle->setMaskedJoints(_maskedJoints->text().split(QRegExp("\\s*,\\s*")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationPanel::removeHandle() {
|
||||||
|
Application::getInstance()->getAvatar()->removeAnimationHandle(_handle);
|
||||||
|
deleteLater();
|
||||||
|
}
|
72
interface/src/ui/AnimationsDialog.h
Normal file
72
interface/src/ui/AnimationsDialog.h
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
//
|
||||||
|
// AnimationsDialog.h
|
||||||
|
// interface/src/ui
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 5/19/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_AnimationsDialog_h
|
||||||
|
#define hifi_AnimationsDialog_h
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QFrame>
|
||||||
|
|
||||||
|
#include "avatar/MyAvatar.h"
|
||||||
|
|
||||||
|
class QDoubleSpinner;
|
||||||
|
class QLineEdit;
|
||||||
|
class QPushButton;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
/// Allows users to edit the avatar animations.
|
||||||
|
class AnimationsDialog : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AnimationsDialog();
|
||||||
|
|
||||||
|
virtual void setVisible(bool visible);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void addAnimation();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QVBoxLayout* _animations;
|
||||||
|
QPushButton* _ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A panel controlling a single animation.
|
||||||
|
class AnimationPanel : public QFrame {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void chooseURL();
|
||||||
|
void chooseMaskedJoints();
|
||||||
|
void updateHandle();
|
||||||
|
void removeHandle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
AnimationsDialog* _dialog;
|
||||||
|
AnimationHandlePointer _handle;
|
||||||
|
QLineEdit* _url;
|
||||||
|
QDoubleSpinBox* _fps;
|
||||||
|
QDoubleSpinBox* _priority;
|
||||||
|
QLineEdit* _maskedJoints;
|
||||||
|
QPushButton* _chooseMaskedJoints;
|
||||||
|
bool _applying;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AnimationsDialog_h
|
|
@ -57,6 +57,8 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog
|
||||||
resize(INITIAL_WIDTH, static_cast<int>(screen.height() * INITIAL_HEIGHT_RATIO));
|
resize(INITIAL_WIDTH, static_cast<int>(screen.height() * INITIAL_HEIGHT_RATIO));
|
||||||
move(screen.center() - rect().center());
|
move(screen.center() - rect().center());
|
||||||
setMinimumWidth(MINIMAL_WIDTH);
|
setMinimumWidth(MINIMAL_WIDTH);
|
||||||
|
|
||||||
|
connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogDialog::~LogDialog() {
|
LogDialog::~LogDialog() {
|
||||||
|
@ -105,7 +107,6 @@ void LogDialog::initControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogDialog::showEvent(QShowEvent*) {
|
void LogDialog::showEvent(QShowEvent*) {
|
||||||
connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection);
|
|
||||||
showLogData();
|
showLogData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +123,6 @@ void LogDialog::appendLogLine(QString logLine) {
|
||||||
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
|
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
|
||||||
_logTextBox->appendPlainText(logLine.simplified());
|
_logTextBox->appendPlainText(logLine.simplified());
|
||||||
}
|
}
|
||||||
_logTextBox->ensureCursorVisible();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,10 +146,8 @@ void LogDialog::handleSearchTextChanged(const QString searchText) {
|
||||||
|
|
||||||
void LogDialog::showLogData() {
|
void LogDialog::showLogData() {
|
||||||
_logTextBox->clear();
|
_logTextBox->clear();
|
||||||
QStringList _logData = _logger->getLogData();
|
_logTextBox->insertPlainText(_logger->getLogData());
|
||||||
for (int i = 0; i < _logData.size(); ++i) {
|
_logTextBox->ensureCursorVisible();
|
||||||
appendLogLine(_logData[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() {
|
KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() {
|
||||||
|
|
|
@ -351,6 +351,7 @@ bool ModelHandler::parseHeaders(QNetworkReply* reply) {
|
||||||
|
|
||||||
QList<QStandardItem*> items = _model.findItems(QFileInfo(reply->url().toString()).baseName());
|
QList<QStandardItem*> items = _model.findItems(QFileInfo(reply->url().toString()).baseName());
|
||||||
if (items.isEmpty() || items.first()->text() == DO_NOT_MODIFY_TAG) {
|
if (items.isEmpty() || items.first()->text() == DO_NOT_MODIFY_TAG) {
|
||||||
|
_lock.unlock();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,6 @@ void NodeBounds::draw() {
|
||||||
glTranslatef(selectedCenter.x, selectedCenter.y, selectedCenter.z);
|
glTranslatef(selectedCenter.x, selectedCenter.y, selectedCenter.z);
|
||||||
glScalef(selectedScale, selectedScale, selectedScale);
|
glScalef(selectedScale, selectedScale, selectedScale);
|
||||||
|
|
||||||
NodeType_t selectedNodeType = selectedNode->getType();
|
|
||||||
float red, green, blue;
|
float red, green, blue;
|
||||||
getColorForNodeType(selectedNode->getType(), red, green, blue);
|
getColorForNodeType(selectedNode->getType(), red, green, blue);
|
||||||
|
|
||||||
|
@ -225,7 +224,6 @@ void NodeBounds::drawOverlay() {
|
||||||
const int FONT = 2;
|
const int FONT = 2;
|
||||||
const int PADDING = 10;
|
const int PADDING = 10;
|
||||||
const int MOUSE_OFFSET = 10;
|
const int MOUSE_OFFSET = 10;
|
||||||
const int BACKGROUND_OFFSET_Y = -20;
|
|
||||||
const int BACKGROUND_BEVEL = 3;
|
const int BACKGROUND_BEVEL = 3;
|
||||||
|
|
||||||
int mouseX = application->getMouseX(),
|
int mouseX = application->getMouseX(),
|
||||||
|
|
|
@ -61,6 +61,24 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c
|
||||||
} // fall through to piggyback message
|
} // fall through to piggyback message
|
||||||
|
|
||||||
voxelPacketType = packetTypeForPacket(mutablePacket);
|
voxelPacketType = packetTypeForPacket(mutablePacket);
|
||||||
|
PacketVersion packetVersion = mutablePacket[1];
|
||||||
|
PacketVersion expectedVersion = versionForPacketType(voxelPacketType);
|
||||||
|
|
||||||
|
// check version of piggyback packet against expected version
|
||||||
|
if (packetVersion != expectedVersion) {
|
||||||
|
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
|
||||||
|
|
||||||
|
QUuid senderUUID = uuidFromPacketHeader(packet);
|
||||||
|
if (!versionDebugSuppressMap.contains(senderUUID, voxelPacketType)) {
|
||||||
|
qDebug() << "Packet version mismatch on" << voxelPacketType << "- Sender"
|
||||||
|
<< senderUUID << "sent" << (int)packetVersion << "but"
|
||||||
|
<< (int)expectedVersion << "expected.";
|
||||||
|
|
||||||
|
versionDebugSuppressMap.insert(senderUUID, voxelPacketType);
|
||||||
|
}
|
||||||
|
return; // bail since piggyback version doesn't match
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
||||||
app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket);
|
app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket);
|
||||||
|
|
|
@ -1399,7 +1399,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
|
|
||||||
// get offset transform from mapping
|
// get offset transform from mapping
|
||||||
float offsetScale = mapping.value("scale", 1.0f).toFloat();
|
float offsetScale = mapping.value("scale", 1.0f).toFloat();
|
||||||
geometry.fstScaled = offsetScale;
|
|
||||||
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
|
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
|
||||||
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
|
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
|
||||||
geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
||||||
|
@ -1443,7 +1442,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
// figure the number of animation frames from the curves
|
// figure the number of animation frames from the curves
|
||||||
int frameCount = 0;
|
int frameCount = 1;
|
||||||
foreach (const AnimationCurve& curve, animationCurves) {
|
foreach (const AnimationCurve& curve, animationCurves) {
|
||||||
frameCount = qMax(frameCount, curve.values.size());
|
frameCount = qMax(frameCount, curve.values.size());
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,8 +211,6 @@ public:
|
||||||
Extents bindExtents;
|
Extents bindExtents;
|
||||||
Extents meshExtents;
|
Extents meshExtents;
|
||||||
|
|
||||||
float fstScaled;
|
|
||||||
|
|
||||||
QVector<FBXAnimationFrame> animationFrames;
|
QVector<FBXAnimationFrame> animationFrames;
|
||||||
|
|
||||||
QVector<FBXAttachment> attachments;
|
QVector<FBXAttachment> attachments;
|
||||||
|
|
|
@ -56,6 +56,7 @@ const QString MODEL_DEFAULT_ANIMATION_URL("");
|
||||||
const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f;
|
const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f;
|
||||||
|
|
||||||
const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1;
|
const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1;
|
||||||
|
const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2;
|
||||||
|
|
||||||
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
|
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
|
||||||
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
|
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
|
||||||
|
|
|
@ -41,6 +41,7 @@ public:
|
||||||
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
|
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
|
||||||
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
|
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
|
||||||
|
|
||||||
|
virtual bool rootElementHasData() const { return true; }
|
||||||
virtual void update();
|
virtual void update();
|
||||||
|
|
||||||
void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer());
|
void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer());
|
||||||
|
|
|
@ -95,6 +95,11 @@ bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
|
||||||
if (_box.contains(clampedMin) && _box.contains(clampedMax)) {
|
if (_box.contains(clampedMin) && _box.contains(clampedMax)) {
|
||||||
int childForMinimumPoint = getMyChildContainingPoint(clampedMin);
|
int childForMinimumPoint = getMyChildContainingPoint(clampedMin);
|
||||||
int childForMaximumPoint = getMyChildContainingPoint(clampedMax);
|
int childForMaximumPoint = getMyChildContainingPoint(clampedMax);
|
||||||
|
|
||||||
|
// if this is a really small box, then it's close enough!
|
||||||
|
if (_box.getScale() <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// If I contain both the minimum and maximum point, but two different children of mine
|
// If I contain both the minimum and maximum point, but two different children of mine
|
||||||
// contain those points, then I am the best fit for that model
|
// contain those points, then I am the best fit for that model
|
||||||
if (childForMinimumPoint != childForMaximumPoint) {
|
if (childForMinimumPoint != childForMaximumPoint) {
|
||||||
|
@ -324,6 +329,14 @@ bool ModelTreeElement::removeModelWithID(uint32_t id) {
|
||||||
int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
ReadBitstreamToTreeParams& args) {
|
ReadBitstreamToTreeParams& args) {
|
||||||
|
|
||||||
|
// If we're the root, but this bitstream doesn't support root elements with data, then
|
||||||
|
// return without reading any bytes
|
||||||
|
if (this == _myTree->getRoot() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
|
||||||
|
qDebug() << "ROOT ELEMENT: no root data for "
|
||||||
|
"bitstreamVersion=" << (int)args.bitstreamVersion << " bytesLeftToRead=" << bytesLeftToRead;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const unsigned char* dataAt = data;
|
const unsigned char* dataAt = data;
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
uint16_t numberOfModels = 0;
|
uint16_t numberOfModels = 0;
|
||||||
|
|
|
@ -136,7 +136,13 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
|
||||||
QNetworkRequest authenticatedRequest;
|
QNetworkRequest authenticatedRequest;
|
||||||
|
|
||||||
QUrl requestURL = _authURL;
|
QUrl requestURL = _authURL;
|
||||||
|
|
||||||
|
if (path.startsWith("/")) {
|
||||||
requestURL.setPath(path);
|
requestURL.setPath(path);
|
||||||
|
} else {
|
||||||
|
requestURL.setPath("/" + path);
|
||||||
|
}
|
||||||
|
|
||||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||||
|
|
||||||
authenticatedRequest.setUrl(requestURL);
|
authenticatedRequest.setUrl(requestURL);
|
||||||
|
|
|
@ -63,7 +63,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
|
||||||
_pool(pool),
|
_pool(pool),
|
||||||
_location(location),
|
_location(location),
|
||||||
_payload(),
|
_payload(),
|
||||||
_isStatic(false)
|
_isStatic(false),
|
||||||
|
_walletUUID()
|
||||||
{
|
{
|
||||||
if (_command == Assignment::CreateCommand) {
|
if (_command == Assignment::CreateCommand) {
|
||||||
// this is a newly created assignment, generate a random UUID
|
// this is a newly created assignment, generate a random UUID
|
||||||
|
@ -74,7 +75,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
|
||||||
Assignment::Assignment(const QByteArray& packet) :
|
Assignment::Assignment(const QByteArray& packet) :
|
||||||
_pool(),
|
_pool(),
|
||||||
_location(GlobalLocation),
|
_location(GlobalLocation),
|
||||||
_payload()
|
_payload(),
|
||||||
|
_walletUUID()
|
||||||
{
|
{
|
||||||
PacketType packetType = packetTypeForPacket(packet);
|
PacketType packetType = packetTypeForPacket(packet);
|
||||||
|
|
||||||
|
@ -104,6 +106,7 @@ Assignment::Assignment(const Assignment& otherAssignment) {
|
||||||
_location = otherAssignment._location;
|
_location = otherAssignment._location;
|
||||||
_pool = otherAssignment._pool;
|
_pool = otherAssignment._pool;
|
||||||
_payload = otherAssignment._payload;
|
_payload = otherAssignment._payload;
|
||||||
|
_walletUUID = otherAssignment._walletUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assignment& Assignment::operator=(const Assignment& rhsAssignment) {
|
Assignment& Assignment::operator=(const Assignment& rhsAssignment) {
|
||||||
|
@ -121,6 +124,7 @@ void Assignment::swap(Assignment& otherAssignment) {
|
||||||
swap(_location, otherAssignment._location);
|
swap(_location, otherAssignment._location);
|
||||||
swap(_pool, otherAssignment._pool);
|
swap(_pool, otherAssignment._pool);
|
||||||
swap(_payload, otherAssignment._payload);
|
swap(_payload, otherAssignment._payload);
|
||||||
|
swap(_walletUUID, otherAssignment._walletUUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Assignment::getTypeName() const {
|
const char* Assignment::getTypeName() const {
|
||||||
|
@ -156,6 +160,10 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) {
|
||||||
QDataStream& operator<<(QDataStream &out, const Assignment& assignment) {
|
QDataStream& operator<<(QDataStream &out, const Assignment& assignment) {
|
||||||
out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload;
|
out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload;
|
||||||
|
|
||||||
|
if (assignment._command == Assignment::RequestCommand) {
|
||||||
|
out << assignment._walletUUID;
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,5 +174,9 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) {
|
||||||
|
|
||||||
in >> assignment._uuid >> assignment._pool >> assignment._payload;
|
in >> assignment._uuid >> assignment._pool >> assignment._payload;
|
||||||
|
|
||||||
|
if (assignment._command == Assignment::RequestCommand) {
|
||||||
|
in >> assignment._walletUUID;
|
||||||
|
}
|
||||||
|
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,9 @@ public:
|
||||||
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
|
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
|
||||||
bool isStatic() const { return _isStatic; }
|
bool isStatic() const { return _isStatic; }
|
||||||
|
|
||||||
|
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||||
|
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||||
|
|
||||||
const char* getTypeName() const;
|
const char* getTypeName() const;
|
||||||
|
|
||||||
// implement parseData to return 0 so we can be a subclass of NodeData
|
// implement parseData to return 0 so we can be a subclass of NodeData
|
||||||
|
@ -98,6 +101,7 @@ protected:
|
||||||
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
|
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
|
||||||
QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed
|
QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed
|
||||||
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
|
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
|
||||||
|
QUuid _walletUUID; /// the UUID for the wallet that should be paid for this assignment
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Assignment_h
|
#endif // hifi_Assignment_h
|
||||||
|
|
|
@ -152,10 +152,11 @@ void LimitedNodeList::changeSendSocketBufferSize(int numSendBytes) {
|
||||||
|
|
||||||
bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) {
|
bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) {
|
||||||
PacketType checkType = packetTypeForPacket(packet);
|
PacketType checkType = packetTypeForPacket(packet);
|
||||||
if (packet[1] != versionForPacketType(checkType)
|
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
|
||||||
|
|
||||||
|
if (packet[numPacketTypeBytes] != versionForPacketType(checkType)
|
||||||
&& checkType != PacketTypeStunResponse) {
|
&& checkType != PacketTypeStunResponse) {
|
||||||
PacketType mismatchType = packetTypeForPacket(packet);
|
PacketType mismatchType = packetTypeForPacket(packet);
|
||||||
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
|
|
||||||
|
|
||||||
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
|
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,6 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeEnvironmentData:
|
case PacketTypeEnvironmentData:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeParticleData:
|
|
||||||
return 1;
|
|
||||||
case PacketTypeDomainList:
|
case PacketTypeDomainList:
|
||||||
case PacketTypeDomainListRequest:
|
case PacketTypeDomainListRequest:
|
||||||
return 3;
|
return 3;
|
||||||
|
@ -66,8 +64,10 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeOctreeStats:
|
case PacketTypeOctreeStats:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeModelData:
|
case PacketTypeParticleData:
|
||||||
return 1;
|
return 1;
|
||||||
|
case PacketTypeModelData:
|
||||||
|
return 2;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ enum PacketType {
|
||||||
PacketTypeVoxelSet,
|
PacketTypeVoxelSet,
|
||||||
PacketTypeVoxelSetDestructive,
|
PacketTypeVoxelSetDestructive,
|
||||||
PacketTypeVoxelErase,
|
PacketTypeVoxelErase,
|
||||||
PacketTypeOctreeStats,
|
PacketTypeOctreeStats, // 26
|
||||||
PacketTypeJurisdiction,
|
PacketTypeJurisdiction,
|
||||||
PacketTypeJurisdictionRequest,
|
PacketTypeJurisdictionRequest,
|
||||||
PacketTypeParticleQuery,
|
PacketTypeParticleQuery,
|
||||||
|
@ -62,7 +62,7 @@ enum PacketType {
|
||||||
PacketTypeDomainServerRequireDTLS,
|
PacketTypeDomainServerRequireDTLS,
|
||||||
PacketTypeNodeJsonStats,
|
PacketTypeNodeJsonStats,
|
||||||
PacketTypeModelQuery,
|
PacketTypeModelQuery,
|
||||||
PacketTypeModelData,
|
PacketTypeModelData, // 41
|
||||||
PacketTypeModelAddOrEdit,
|
PacketTypeModelAddOrEdit,
|
||||||
PacketTypeModelErase,
|
PacketTypeModelErase,
|
||||||
PacketTypeModelAddResponse,
|
PacketTypeModelAddResponse,
|
||||||
|
|
|
@ -301,6 +301,13 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this is the root, and there is more data to read, allow it to read it's element data...
|
||||||
|
if (destinationElement == _rootElement && rootElementHasData() && (bytesLeftToRead - bytesRead) > 0) {
|
||||||
|
// tell the element to read the subsequent data
|
||||||
|
bytesRead += _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead - bytesRead, args);
|
||||||
|
}
|
||||||
|
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1524,7 +1531,22 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element,
|
||||||
}
|
}
|
||||||
} // end keepDiggingDeeper
|
} // end keepDiggingDeeper
|
||||||
|
|
||||||
// At this point all our BitMasks are complete... so let's output them to see how they compare...
|
// If we made it this far, then we've written all of our child data... if this element is the root
|
||||||
|
// element, then we also allow the root element to write out it's data...
|
||||||
|
if (continueThisLevel && element == _rootElement && rootElementHasData()) {
|
||||||
|
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||||
|
continueThisLevel = element->appendElementData(packetData, params);
|
||||||
|
int bytesAfterChild = packetData->getUncompressedSize();
|
||||||
|
|
||||||
|
if (continueThisLevel) {
|
||||||
|
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||||
|
|
||||||
|
if (params.stats) {
|
||||||
|
params.stats->colorSent(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if we were unable to fit this level in our packet, then rewind and add it to the element bag for
|
// if we were unable to fit this level in our packet, then rewind and add it to the element bag for
|
||||||
// sending later...
|
// sending later...
|
||||||
if (continueThisLevel) {
|
if (continueThisLevel) {
|
||||||
|
|
|
@ -211,6 +211,7 @@ public:
|
||||||
const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; }
|
const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; }
|
||||||
|
|
||||||
virtual bool recurseChildrenWithData() const { return true; }
|
virtual bool recurseChildrenWithData() const { return true; }
|
||||||
|
virtual bool rootElementHasData() const { return false; }
|
||||||
|
|
||||||
|
|
||||||
virtual void update() { }; // nothing to do by default
|
virtual void update() { }; // nothing to do by default
|
||||||
|
|
|
@ -1413,6 +1413,11 @@ OctreeElement* OctreeElement::getOrCreateChildElementContaining(const AABox& box
|
||||||
child = addChildAtIndex(childIndex);
|
child = addChildAtIndex(childIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we've made a really small child, then go ahead and use that one.
|
||||||
|
if (child->getScale() <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we have the child to recurse down, let it answer the original question...
|
// Now that we have the child to recurse down, let it answer the original question...
|
||||||
return child->getOrCreateChildElementContaining(box);
|
return child->getOrCreateChildElementContaining(box);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ class OctreePacketData;
|
||||||
class ReadBitstreamToTreeParams;
|
class ReadBitstreamToTreeParams;
|
||||||
class VoxelSystem;
|
class VoxelSystem;
|
||||||
|
|
||||||
|
const float SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE = (1.0f / TREE_SCALE) / 10000.0f; // 1/10,000th of a meter
|
||||||
|
|
||||||
// Callers who want delete hook callbacks should implement this class
|
// Callers who want delete hook callbacks should implement this class
|
||||||
class OctreeElementDeleteHook {
|
class OctreeElementDeleteHook {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -41,19 +41,19 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
|
||||||
|
|
||||||
nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1);
|
nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1);
|
||||||
|
|
||||||
if (nextKeyIndex == keyIndex + 1) {
|
if (nextKeyIndex == keyIndex + 1 || keyIndex == argumentList.size() - 1) {
|
||||||
// there's no value associated with this option, it's a boolean
|
// there's no value associated with this option, it's a boolean
|
||||||
// so add it to the variant map with NULL as value
|
// so add it to the variant map with NULL as value
|
||||||
mergedMap.insertMulti(key, QVariant());
|
mergedMap.insertMulti(key, QVariant());
|
||||||
} else {
|
} else {
|
||||||
int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex;
|
int maxIndex = (nextKeyIndex == -1) ? argumentList.size() - 1: nextKeyIndex;
|
||||||
|
|
||||||
// there's at least one value associated with the option
|
// there's at least one value associated with the option
|
||||||
// pull the first value to start
|
// pull the first value to start
|
||||||
QString value = argumentList[keyIndex + 1];
|
QString value = argumentList[keyIndex + 1];
|
||||||
|
|
||||||
// for any extra values, append them, with a space, to the value string
|
// for any extra values, append them, with a space, to the value string
|
||||||
for (int i = keyIndex + 2; i < maxIndex; i++) {
|
for (int i = keyIndex + 2; i <= maxIndex; i++) {
|
||||||
value += " " + argumentList[i];
|
value += " " + argumentList[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue