mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 18:23:54 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels
This commit is contained in:
commit
53fc31353d
71 changed files with 1814 additions and 375 deletions
|
@ -1,9 +1,17 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
cmake_minimum_required(VERSION 2.8.12.2)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
cmake_policy(SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
if (POLICY CMP0028)
|
||||
cmake_policy(SET CMP0028 OLD)
|
||||
endif ()
|
||||
|
||||
if (POLICY CMP0043)
|
||||
cmake_policy(SET CMP0043 OLD)
|
||||
endif ()
|
||||
|
||||
project(hifi)
|
||||
add_definitions(-DGLM_FORCE_RADIANS)
|
||||
|
||||
|
@ -39,7 +47,7 @@ set(CMAKE_AUTOMOC ON)
|
|||
# targets not supported on windows
|
||||
if (NOT WIN32)
|
||||
add_subdirectory(animation-server)
|
||||
endif (NOT WIN32)
|
||||
endif ()
|
||||
|
||||
# targets on all platforms
|
||||
add_subdirectory(assignment-client)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME assignment-client)
|
||||
|
||||
set(ROOT_DIR ..)
|
||||
|
|
|
@ -395,41 +395,25 @@ void AudioMixer::run() {
|
|||
|
||||
nodeList->linkedDataCreateCallback = attachNewNodeDataToNode;
|
||||
|
||||
// setup a NetworkAccessManager to ask the domain-server for our settings
|
||||
NetworkAccessManager& networkManager = NetworkAccessManager::getInstance();
|
||||
// wait until we have the domain-server settings, otherwise we bail
|
||||
DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
QUrl settingsJSONURL;
|
||||
settingsJSONURL.setScheme("http");
|
||||
settingsJSONURL.setHost(nodeList->getDomainHandler().getHostname());
|
||||
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
|
||||
settingsJSONURL.setPath("/settings.json");
|
||||
settingsJSONURL.setQuery(QString("type=%1").arg(_type));
|
||||
qDebug() << "Waiting for domain settings from domain-server.";
|
||||
|
||||
QNetworkReply *reply = NULL;
|
||||
// block until we get the settingsRequestComplete signal
|
||||
QEventLoop loop;
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
|
||||
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
|
||||
domainHandler.requestDomainSettings();
|
||||
loop.exec();
|
||||
|
||||
int failedAttempts = 0;
|
||||
const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
|
||||
|
||||
qDebug() << "Requesting settings for assignment from domain-server at" << settingsJSONURL.toString();
|
||||
|
||||
while (!reply || reply->error() != QNetworkReply::NoError) {
|
||||
reply = networkManager.get(QNetworkRequest(settingsJSONURL));
|
||||
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
|
||||
loop.exec();
|
||||
|
||||
++failedAttempts;
|
||||
|
||||
if (failedAttempts == MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
|
||||
qDebug() << "Failed to get settings from domain-server. Bailing on assignment.";
|
||||
setFinished(true);
|
||||
return;
|
||||
}
|
||||
if (domainHandler.getSettingsObject().isEmpty()) {
|
||||
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
|
||||
setFinished(true);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject settingsObject = QJsonDocument::fromJson(reply->readAll()).object();
|
||||
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
|
||||
|
||||
// check the settings object to see if we have anything we can parse out
|
||||
const QString AUDIO_GROUP_KEY = "audio";
|
||||
|
|
|
@ -9,11 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <Logging.h>
|
||||
#include <UUID.h>
|
||||
|
@ -244,6 +247,10 @@ OctreeServer::OctreeServer(const QByteArray& packet) :
|
|||
|
||||
_averageLoopTime.updateAverage(0);
|
||||
qDebug() << "Octree server starting... [" << this << "]";
|
||||
|
||||
// make sure the AccountManager has an Auth URL for payment redemptions
|
||||
|
||||
AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
|
||||
}
|
||||
|
||||
OctreeServer::~OctreeServer() {
|
||||
|
@ -857,6 +864,8 @@ void OctreeServer::readPendingDatagrams() {
|
|||
}
|
||||
} else if (packetType == PacketTypeJurisdictionRequest) {
|
||||
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else if (packetType == PacketTypeSignedTransactionPayment) {
|
||||
handleSignedTransactionPayment(packetType, receivedPacket);
|
||||
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
|
||||
} else {
|
||||
|
@ -1204,6 +1213,51 @@ QString OctreeServer::getStatusLink() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void OctreeServer::handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram) {
|
||||
// for now we're not verifying that this is actual payment for any octree edits
|
||||
// just use the AccountManager to send it up to the data server and have it redeemed
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE = 72;
|
||||
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE = 256;
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(packetType);
|
||||
|
||||
// pull out the transaction message in binary
|
||||
QByteArray messageHex = datagram.mid(numBytesPacketHeader, NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE).toHex();
|
||||
// pull out the binary signed message digest
|
||||
QByteArray signatureHex = datagram.mid(numBytesPacketHeader + NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE,
|
||||
NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE).toHex();
|
||||
|
||||
// setup the QJSONObject we are posting
|
||||
QJsonObject postObject;
|
||||
|
||||
const QString TRANSACTION_OBJECT_MESSAGE_KEY = "message";
|
||||
const QString TRANSACTION_OBJECT_SIGNATURE_KEY = "signature";
|
||||
const QString POST_OBJECT_TRANSACTION_KEY = "transaction";
|
||||
|
||||
QJsonObject transactionObject;
|
||||
transactionObject.insert(TRANSACTION_OBJECT_MESSAGE_KEY, QString(messageHex));
|
||||
transactionObject.insert(TRANSACTION_OBJECT_SIGNATURE_KEY, QString(signatureHex));
|
||||
|
||||
postObject.insert(POST_OBJECT_TRANSACTION_KEY, transactionObject);
|
||||
|
||||
// setup our callback params
|
||||
JSONCallbackParameters callbackParameters;
|
||||
callbackParameters.jsonCallbackReceiver = this;
|
||||
callbackParameters.jsonCallbackMethod = "handleSignedTransactionPaymentResponse";
|
||||
|
||||
accountManager.unauthenticatedRequest("/api/v1/transactions/redeem", QNetworkAccessManager::PostOperation,
|
||||
callbackParameters, QJsonDocument(postObject).toJson());
|
||||
|
||||
}
|
||||
|
||||
void OctreeServer::handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject) {
|
||||
// pull the ID to debug the transaction
|
||||
QString transactionIDString = jsonObject["data"].toObject()["transaction"].toObject()["id"].toString();
|
||||
qDebug() << "Redeemed transaction with ID" << transactionIDString << "successfully.";
|
||||
}
|
||||
|
||||
void OctreeServer::sendStatsPacket() {
|
||||
// TODO: we have too many stats to fit in a single MTU... so for now, we break it into multiple JSON objects and
|
||||
// send them separately. What we really should do is change the NodeList::sendStatsToDomainServer() to handle the
|
||||
|
|
|
@ -127,6 +127,8 @@ public slots:
|
|||
void nodeAdded(SharedNodePointer node);
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
void sendStatsPacket();
|
||||
|
||||
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
|
||||
|
||||
protected:
|
||||
void parsePayload();
|
||||
|
@ -137,6 +139,8 @@ protected:
|
|||
QString getConfiguration();
|
||||
QString getStatusLink();
|
||||
|
||||
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
|
||||
|
||||
int _argc;
|
||||
const char** _argv;
|
||||
char** _parsedArgV;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
||||
macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
||||
project(${TARGET})
|
||||
|
||||
# grab the implemenation and header files
|
||||
|
@ -29,6 +29,6 @@ macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
|
|||
find_package(Qt5Core REQUIRED)
|
||||
qt5_use_modules(${TARGET} Core)
|
||||
endif ()
|
||||
|
||||
|
||||
target_link_libraries(${TARGET} ${QT_LIBRARIES})
|
||||
endmacro()
|
|
@ -1,5 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<th>Type</th>
|
||||
<th>UUID</th>
|
||||
<th>Pool</th>
|
||||
<th>Username</th>
|
||||
<th>Public</th>
|
||||
<th>Local</th>
|
||||
<th>Uptime (s)</th>
|
||||
|
@ -24,8 +25,9 @@
|
|||
<td><%- node.type %></td>
|
||||
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
|
||||
<td><%- node.pool %></td>
|
||||
<td><%- node.public.ip %><span class='port'><%- node.public.port %></span></td>
|
||||
<td><%- node.local.ip %><span class='port'><%- node.local.port %></span></td>
|
||||
<td><%- node.username %></td>
|
||||
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
|
||||
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
|
||||
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
|
||||
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
|
||||
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>
|
||||
|
|
|
@ -28,5 +28,33 @@
|
|||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"voxels": {
|
||||
"label": "Voxels",
|
||||
"assignment-types": [2,3],
|
||||
"settings": {
|
||||
"voxel-wallet": {
|
||||
"label": "Destination Wallet ID",
|
||||
"help": "Wallet to be paid for voxel changes",
|
||||
"placeholder": "00000000-0000-0000-0000-000000000000",
|
||||
"default": ""
|
||||
},
|
||||
"per-voxel-credits": {
|
||||
"type": "double",
|
||||
"label": "Per Voxel Cost",
|
||||
"help": "Credit cost to change each voxel",
|
||||
"placeholder": "0.0",
|
||||
"default": "0.0",
|
||||
"input_addon": "₵"
|
||||
},
|
||||
"per-meter-cubed-credits": {
|
||||
"type": "double",
|
||||
"label": "Per Meter Cubed Cost",
|
||||
"help": "Credit cost to change each cubed meter of voxel space",
|
||||
"placeholder": "0.0",
|
||||
"default": "0.0",
|
||||
"input_addon": "₵"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,16 +16,22 @@
|
|||
<% var setting_id = group_key + "." + setting_key %>
|
||||
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
|
||||
<div class="col-sm-10">
|
||||
<% if(setting.type) %>
|
||||
<% if (setting.type === "checkbox") { %>
|
||||
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
|
||||
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
|
||||
<% } else { %>
|
||||
<% if (setting.type === "checkbox") { %>
|
||||
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
|
||||
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
|
||||
<% } else { %>
|
||||
<% if (setting.input_addon) { %>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><%- setting.input_addon %></div>
|
||||
<% } %>
|
||||
<input type="text" class="form-control" id="<%- setting_id %>"
|
||||
placeholder="<%- setting.placeholder %>"
|
||||
value="<%- (values[group_key] || {})[setting_key] %>">
|
||||
|
||||
<% if (setting.input_addon) { %>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
<p class="help-block col-sm-offset-2 col-sm-10"><%- setting.help %></p>
|
||||
</div>
|
||||
|
|
|
@ -42,12 +42,17 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_hostname(),
|
||||
_networkReplyUUIDMap(),
|
||||
_sessionAuthenticationHash(),
|
||||
_webAuthenticationStateSet(),
|
||||
_cookieSessionHash(),
|
||||
_settingsManager()
|
||||
{
|
||||
setOrganizationName("High Fidelity");
|
||||
setOrganizationDomain("highfidelity.io");
|
||||
setApplicationName("domain-server");
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
|
||||
|
||||
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
||||
|
||||
|
@ -57,6 +62,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
loadExistingSessionsFromSettings();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,11 +433,14 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
QString connectedUsername;
|
||||
|
||||
if (!isAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
|
||||
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
||||
if (!_sessionAuthenticationHash.value(packetUUID)) {
|
||||
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
|
||||
if (connectedUsername.isEmpty()) {
|
||||
// we've decided this is a user that isn't allowed in, return out
|
||||
// TODO: provide information to the user so they know why they can't connect
|
||||
return;
|
||||
|
@ -473,6 +483,9 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
// now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
|
||||
delete pendingAssigneeData;
|
||||
}
|
||||
|
||||
// if we have a username from an OAuth connect request, set it on the DomainServerNodeData
|
||||
nodeData->setUsername(connectedUsername);
|
||||
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
||||
|
@ -848,7 +861,7 @@ QJsonObject DomainServer::jsonForSocket(const HifiSockAddr& socket) {
|
|||
QJsonObject socketJSON;
|
||||
|
||||
socketJSON["ip"] = socket.getAddress().toString();
|
||||
socketJSON["port"] = ntohs(socket.getPort());
|
||||
socketJSON["port"] = socket.getPort();
|
||||
|
||||
return socketJSON;
|
||||
}
|
||||
|
@ -860,6 +873,7 @@ const char JSON_KEY_LOCAL_SOCKET[] = "local";
|
|||
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_USERNAME[] = "username";
|
||||
|
||||
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
||||
QJsonObject nodeJson;
|
||||
|
@ -884,6 +898,10 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
|||
|
||||
// if the node has pool information, add it
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
// add the node username, if it exists
|
||||
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
|
||||
|
||||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||
if (matchingAssignment) {
|
||||
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
|
||||
|
@ -921,7 +939,19 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
const QString URI_NODES = "/nodes";
|
||||
|
||||
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
||||
|
||||
|
||||
// allow sub-handlers to handle requests that do not require authentication
|
||||
if (_settingsManager.handlePublicHTTPRequest(connection, url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// all requests below require a cookie to prove authentication so check that first
|
||||
if (!isAuthenticatedRequest(connection, url)) {
|
||||
// this is not an authenticated request
|
||||
// return true from the handler since it was handled with a 401 or re-direct to auth
|
||||
return true;
|
||||
}
|
||||
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
||||
if (url.path() == "/assignments.json") {
|
||||
// user is asking for json list of assignments
|
||||
|
@ -1162,9 +1192,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
}
|
||||
|
||||
// didn't process the request, let our DomainServerSettingsManager or HTTPManager handle
|
||||
return _settingsManager.handleHTTPRequest(connection, url);
|
||||
return _settingsManager.handleAuthenticatedHTTPRequest(connection, url);
|
||||
}
|
||||
|
||||
const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
|
||||
|
||||
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
|
||||
const QString URI_OAUTH = "/oauth";
|
||||
qDebug() << "HTTPS request received at" << url.toString();
|
||||
|
@ -1178,7 +1210,6 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
|||
const QString STATE_QUERY_KEY = "state";
|
||||
QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY));
|
||||
|
||||
|
||||
if (!authorizationCode.isEmpty() && !stateUUID.isNull()) {
|
||||
// fire off a request with this code and state to get an access token for the user
|
||||
|
||||
|
@ -1193,15 +1224,45 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
|||
|
||||
QNetworkRequest tokenRequest(tokenRequestUrl);
|
||||
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
|
||||
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
|
||||
|
||||
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
|
||||
|
||||
// insert this to our pending token replies so we can associate the returned access token with the right UUID
|
||||
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
|
||||
|
||||
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
|
||||
|
||||
if (_webAuthenticationStateSet.remove(stateUUID)) {
|
||||
// this is a web user who wants to auth to access web interface
|
||||
// we hold the response back to them until we get their profile information
|
||||
// and can decide if they are let in or not
|
||||
|
||||
QEventLoop loop;
|
||||
connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
// start the loop for the token request
|
||||
loop.exec();
|
||||
|
||||
QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply);
|
||||
|
||||
// stop the loop once the profileReply is complete
|
||||
connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
// restart the loop for the profile request
|
||||
loop.exec();
|
||||
|
||||
// call helper method to get cookieHeaders
|
||||
Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply);
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode302, QByteArray(),
|
||||
HTTPConnection::DefaultContentType, cookieHeaders);
|
||||
|
||||
// we've redirected the user back to our homepage
|
||||
return true;
|
||||
|
||||
} else {
|
||||
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
|
||||
|
||||
// insert this to our pending token replies so we can associate the returned access token with the right UUID
|
||||
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
|
||||
|
||||
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
|
||||
}
|
||||
}
|
||||
|
||||
// respond with a 200 code indicating that login is complete
|
||||
|
@ -1213,6 +1274,77 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
|||
}
|
||||
}
|
||||
|
||||
bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) {
|
||||
|
||||
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
||||
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
||||
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
|
||||
|
||||
if (!_oauthProviderURL.isEmpty()
|
||||
&& (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
|
||||
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
|
||||
|
||||
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
|
||||
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
|
||||
|
||||
QUuid cookieUUID;
|
||||
if (cookieString.indexOf(cookieUUIDRegex) != -1) {
|
||||
cookieUUID = cookieUUIDRegex.cap(1);
|
||||
}
|
||||
|
||||
if (!cookieUUID.isNull() && _cookieSessionHash.contains(cookieUUID)) {
|
||||
// pull the QJSONObject for the user with this cookie UUID
|
||||
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
|
||||
QString profileUsername = sessionData.getUsername();
|
||||
|
||||
if (_argumentVariantMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().contains(profileUsername)) {
|
||||
// this is an authenticated user
|
||||
return true;
|
||||
}
|
||||
|
||||
// loop the roles of this user and see if they are in the admin-roles array
|
||||
QJsonArray adminRolesArray = _argumentVariantMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
|
||||
|
||||
if (!adminRolesArray.isEmpty()) {
|
||||
foreach(const QString& userRole, sessionData.getRoles()) {
|
||||
if (adminRolesArray.contains(userRole)) {
|
||||
// this user has a role that allows them to administer the domain-server
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString unauthenticatedRequest = "You do not have permission to access this domain-server.";
|
||||
connection->respond(HTTPConnection::StatusCode401, unauthenticatedRequest.toUtf8());
|
||||
|
||||
// the user does not have allowed username or role, return 401
|
||||
return false;
|
||||
} else {
|
||||
// re-direct this user to OAuth page
|
||||
|
||||
// generate a random state UUID to use
|
||||
QUuid stateUUID = QUuid::createUuid();
|
||||
|
||||
// add it to the set so we can handle the callback from the OAuth provider
|
||||
_webAuthenticationStateSet.insert(stateUUID);
|
||||
|
||||
QUrl oauthRedirectURL = oauthAuthorizationURL(stateUUID);
|
||||
|
||||
Headers redirectHeaders;
|
||||
redirectHeaders.insert("Location", oauthRedirectURL.toEncoded());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode302,
|
||||
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
|
||||
|
||||
// we don't know about this user yet, so they are not yet authenticated
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
|
||||
|
||||
void DomainServer::handleTokenRequestFinished() {
|
||||
|
@ -1220,20 +1352,11 @@ void DomainServer::handleTokenRequestFinished() {
|
|||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
||||
|
||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
||||
// pull the access token from the returned JSON and store it with the matching session UUID
|
||||
QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll());
|
||||
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
|
||||
|
||||
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
|
||||
|
||||
// fire off a request to get this user's identity so we can see if we will let them in
|
||||
QUrl profileURL = _oauthProviderURL;
|
||||
profileURL.setPath("/api/v1/users/profile");
|
||||
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
||||
|
||||
QNetworkReply* profileReply = NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
|
||||
|
||||
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
|
||||
|
||||
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
||||
<< "-" << "requesting profile.";
|
||||
|
||||
QNetworkReply* profileReply = profileRequestGivenTokenReply(networkReply);
|
||||
|
||||
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
|
||||
|
||||
|
@ -1241,6 +1364,19 @@ void DomainServer::handleTokenRequestFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
|
||||
// pull the access token from the returned JSON and store it with the matching session UUID
|
||||
QJsonDocument returnedJSON = QJsonDocument::fromJson(tokenReply->readAll());
|
||||
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
|
||||
|
||||
// fire off a request to get this user's identity so we can see if we will let them in
|
||||
QUrl profileURL = _oauthProviderURL;
|
||||
profileURL.setPath("/api/v1/users/profile");
|
||||
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
||||
|
||||
return NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
|
||||
}
|
||||
|
||||
void DomainServer::handleProfileRequestFinished() {
|
||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
||||
|
@ -1254,26 +1390,83 @@ void DomainServer::handleProfileRequestFinished() {
|
|||
|
||||
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
|
||||
|
||||
bool shouldAllowUserToConnect = false;
|
||||
QString connectableUsername;
|
||||
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
|
||||
|
||||
foreach(const QJsonValue& roleValue, userRolesArray) {
|
||||
if (allowedRolesArray.contains(roleValue)) {
|
||||
// the user has a role that lets them in
|
||||
// set the bool to true and break
|
||||
shouldAllowUserToConnect = true;
|
||||
connectableUsername = profileUsername;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
||||
<< "-" << shouldAllowUserToConnect;
|
||||
|
||||
if (connectableUsername.isEmpty()) {
|
||||
qDebug() << "User" << profileUsername << "with session UUID"
|
||||
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
||||
<< "does not have an allowable role. Refusing connection.";
|
||||
} else {
|
||||
qDebug() << "User" << profileUsername << "with session UUID"
|
||||
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
||||
<< "has an allowable role. Can connect.";
|
||||
}
|
||||
|
||||
// insert this UUID and a flag that indicates if they are allowed to connect
|
||||
_sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect);
|
||||
_sessionAuthenticationHash.insert(matchingSessionUUID, connectableUsername);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
|
||||
|
||||
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
|
||||
Headers cookieHeaders;
|
||||
|
||||
// create a UUID for this cookie
|
||||
QUuid cookieUUID = QUuid::createUuid();
|
||||
|
||||
QJsonDocument profileDocument = QJsonDocument::fromJson(profileReply->readAll());
|
||||
QJsonObject userObject = profileDocument.object()["data"].toObject()["user"].toObject();
|
||||
|
||||
// add the profile to our in-memory data structure so we know who the user is when they send us their cookie
|
||||
DomainServerWebSessionData sessionData(userObject);
|
||||
_cookieSessionHash.insert(cookieUUID, sessionData);
|
||||
|
||||
// persist the cookie to settings file so we can get it back on DS relaunch
|
||||
QSettings localSettings;
|
||||
localSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
|
||||
QVariant sessionVariant = QVariant::fromValue(sessionData);
|
||||
localSettings.setValue(cookieUUID.toString(), QVariant::fromValue(sessionData));
|
||||
|
||||
// setup expiry for cookie to 1 month from today
|
||||
QDateTime cookieExpiry = QDateTime::currentDateTimeUtc().addMonths(1);
|
||||
|
||||
QString cookieString = HIFI_SESSION_COOKIE_KEY + "=" + uuidStringWithoutCurlyBraces(cookieUUID.toString());
|
||||
cookieString += "; expires=" + cookieExpiry.toString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
|
||||
cookieString += "; domain=" + _hostname + "; path=/";
|
||||
|
||||
cookieHeaders.insert("Set-Cookie", cookieString.toUtf8());
|
||||
|
||||
// redirect the user back to the homepage so they can present their cookie and be authenticated
|
||||
QString redirectString = "http://" + _hostname + ":" + QString::number(_httpManager.serverPort());
|
||||
cookieHeaders.insert("Location", redirectString.toUtf8());
|
||||
|
||||
return cookieHeaders;
|
||||
}
|
||||
|
||||
void DomainServer::loadExistingSessionsFromSettings() {
|
||||
// read data for existing web sessions into memory so existing sessions can be leveraged
|
||||
QSettings domainServerSettings;
|
||||
domainServerSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
|
||||
|
||||
foreach(const QString& uuidKey, domainServerSettings.childKeys()) {
|
||||
_cookieSessionHash.insert(QUuid(uuidKey), domainServerSettings.value(uuidKey).value<DomainServerWebSessionData>());
|
||||
qDebug() << "Pulled web session from settings - cookie UUID is" << uuidKey;
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
|
||||
QUuid oldUUID = assignment->getUUID();
|
||||
assignment->resetUUID();
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DomainServerSettingsManager.h"
|
||||
#include "DomainServerWebSessionData.h"
|
||||
#include "WalletTransaction.h"
|
||||
|
||||
#include "PendingAssignedNodeData.h"
|
||||
|
@ -85,8 +86,14 @@ private:
|
|||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
|
||||
void handleProfileRequestFinished();
|
||||
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
|
||||
|
||||
void loadExistingSessionsFromSettings();
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
@ -108,7 +115,10 @@ private:
|
|||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
|
||||
QHash<QUuid, bool> _sessionAuthenticationHash;
|
||||
QHash<QUuid, QString> _sessionAuthenticationHash;
|
||||
|
||||
QSet<QUuid> _webAuthenticationStateSet;
|
||||
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
|
||||
|
||||
DomainServerSettingsManager _settingsManager;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ DomainServerNodeData::DomainServerNodeData() :
|
|||
_sessionSecretHash(),
|
||||
_assignmentUUID(),
|
||||
_walletUUID(),
|
||||
_username(),
|
||||
_paymentIntervalTimer(),
|
||||
_statsJSONObject(),
|
||||
_sendingSockAddr(),
|
||||
|
|
|
@ -35,6 +35,9 @@ public:
|
|||
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||
|
||||
void setUsername(const QString& username) { _username = username; }
|
||||
const QString& getUsername() const { return _username; }
|
||||
|
||||
QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
|
||||
|
||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||
|
@ -50,6 +53,7 @@ private:
|
|||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _assignmentUUID;
|
||||
QUuid _walletUUID;
|
||||
QString _username;
|
||||
QElapsedTimer _paymentIntervalTimer;
|
||||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
|
|
|
@ -47,24 +47,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
|||
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
||||
const QString SETTING_DEFAULT_KEY = "default";
|
||||
|
||||
bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
|
||||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
|
||||
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
return true;
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
|
||||
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
|
||||
// this is a GET operation for our settings
|
||||
|
||||
// check if there is a query parameter for settings affecting a particular type of assignment
|
||||
|
@ -135,6 +119,30 @@ bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
|
||||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
|
||||
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||
|
||||
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
QVariantMap& settingsVariant,
|
||||
QJsonObject descriptionObject) {
|
||||
|
@ -145,7 +153,17 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
// we don't continue if this key is not present in our descriptionObject
|
||||
if (descriptionObject.contains(key)) {
|
||||
if (rootValue.isString()) {
|
||||
settingsVariant[key] = rootValue.toString();
|
||||
if (rootValue.toString().isEmpty()) {
|
||||
// this is an empty value, clear it in settings variant so the default is sent
|
||||
settingsVariant.remove(key);
|
||||
} else {
|
||||
if (descriptionObject[key].toObject().contains(SETTING_DESCRIPTION_TYPE_KEY)) {
|
||||
// for now this means that this is a double, so set it as a double
|
||||
settingsVariant[key] = rootValue.toString().toDouble();
|
||||
} else {
|
||||
settingsVariant[key] = rootValue.toString();
|
||||
}
|
||||
}
|
||||
} else if (rootValue.isBool()) {
|
||||
settingsVariant[key] = rootValue.toBool();
|
||||
} else if (rootValue.isObject()) {
|
||||
|
@ -158,9 +176,16 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
settingsVariant[key] = QVariantMap();
|
||||
}
|
||||
|
||||
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[key].data());
|
||||
|
||||
recurseJSONObjectAndOverwriteSettings(rootValue.toObject(),
|
||||
*reinterpret_cast<QVariantMap*>(settingsVariant[key].data()),
|
||||
thisMap,
|
||||
nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toObject());
|
||||
|
||||
if (thisMap.isEmpty()) {
|
||||
// we've cleared all of the settings below this value, so remove this one too
|
||||
settingsVariant.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,12 @@
|
|||
|
||||
#include <HTTPManager.h>
|
||||
|
||||
class DomainServerSettingsManager : public QObject, HTTPRequestHandler {
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServerSettingsManager();
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
QByteArray getJSONSettingsMap() const;
|
||||
private:
|
||||
|
|
63
domain-server/src/DomainServerWebSessionData.cpp
Normal file
63
domain-server/src/DomainServerWebSessionData.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// DomainServerWebSessionData.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-07-21.
|
||||
// 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/QDebug>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include "DomainServerWebSessionData.h"
|
||||
|
||||
DomainServerWebSessionData::DomainServerWebSessionData() :
|
||||
_username(),
|
||||
_roles()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DomainServerWebSessionData::DomainServerWebSessionData(const QJsonObject& userObject) :
|
||||
_roles()
|
||||
{
|
||||
_username = userObject["username"].toString();
|
||||
|
||||
// pull each of the roles and throw them into our set
|
||||
foreach(const QJsonValue& rolesValue, userObject["roles"].toArray()) {
|
||||
_roles.insert(rolesValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) {
|
||||
_username = otherSessionData._username;
|
||||
_roles = otherSessionData._roles;
|
||||
}
|
||||
|
||||
DomainServerWebSessionData& DomainServerWebSessionData::operator=(const DomainServerWebSessionData& otherSessionData) {
|
||||
DomainServerWebSessionData temp(otherSessionData);
|
||||
swap(temp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void DomainServerWebSessionData::swap(DomainServerWebSessionData& otherSessionData) {
|
||||
using std::swap;
|
||||
|
||||
swap(_username, otherSessionData._username);
|
||||
swap(_roles, otherSessionData._roles);
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream &out, const DomainServerWebSessionData& session) {
|
||||
out << session._username << session._roles;
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream &in, DomainServerWebSessionData& session) {
|
||||
in >> session._username >> session._roles;
|
||||
return in;
|
||||
}
|
||||
|
41
domain-server/src/DomainServerWebSessionData.h
Normal file
41
domain-server/src/DomainServerWebSessionData.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// DomainServerWebSessionData.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-07-21.
|
||||
// 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_DomainServerWebSessionData_h
|
||||
#define hifi_DomainServerWebSessionData_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSet>
|
||||
|
||||
class DomainServerWebSessionData : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServerWebSessionData();
|
||||
DomainServerWebSessionData(const QJsonObject& userObject);
|
||||
DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData);
|
||||
DomainServerWebSessionData& operator=(const DomainServerWebSessionData& otherSessionData);
|
||||
|
||||
const QString& getUsername() const { return _username; }
|
||||
const QSet<QString>& getRoles() const { return _roles; }
|
||||
|
||||
friend QDataStream& operator<<(QDataStream &out, const DomainServerWebSessionData& session);
|
||||
friend QDataStream& operator>>(QDataStream &in, DomainServerWebSessionData& session);
|
||||
|
||||
private:
|
||||
void swap(DomainServerWebSessionData& otherSessionData);
|
||||
|
||||
QString _username;
|
||||
QSet<QString> _roles;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(DomainServerWebSessionData)
|
||||
|
||||
#endif // hifi_DomainServerWebSessionData_h
|
674
examples/bot_procedural.js.
Normal file
674
examples/bot_procedural.js.
Normal file
|
@ -0,0 +1,674 @@
|
|||
//
|
||||
// bot_procedural.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Ben Arnold on 7/29/2013
|
||||
//
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is an example script that demonstrates an NPC avatar.
|
||||
//
|
||||
//
|
||||
|
||||
//For procedural walk animation
|
||||
Script.include("http://s3-us-west-1.amazonaws.com/highfidelity-public/scripts/proceduralAnimationAPI.js");
|
||||
|
||||
var procAnimAPI = new ProcAnimAPI();
|
||||
|
||||
function getRandomFloat(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
function getRandomInt (min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function printVector(string, vector) {
|
||||
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
|
||||
}
|
||||
|
||||
var CHANCE_OF_MOVING = 0.005;
|
||||
var CHANCE_OF_SOUND = 0.005;
|
||||
var CHANCE_OF_HEAD_TURNING = 0.01;
|
||||
var CHANCE_OF_BIG_MOVE = 1.0;
|
||||
|
||||
var isMoving = false;
|
||||
var isTurningHead = false;
|
||||
var isPlayingAudio = false;
|
||||
|
||||
var X_MIN = 0.50;
|
||||
var X_MAX = 15.60;
|
||||
var Z_MIN = 0.50;
|
||||
var Z_MAX = 15.10;
|
||||
var Y_FEET = 0.0;
|
||||
var AVATAR_PELVIS_HEIGHT = 0.84;
|
||||
var Y_PELVIS = Y_FEET + AVATAR_PELVIS_HEIGHT;
|
||||
var MAX_PELVIS_DELTA = 2.5;
|
||||
|
||||
var MOVE_RANGE_SMALL = 3.0;
|
||||
var MOVE_RANGE_BIG = 10.0;
|
||||
var TURN_RANGE = 70.0;
|
||||
var STOP_TOLERANCE = 0.05;
|
||||
var MOVE_RATE = 0.05;
|
||||
var TURN_RATE = 0.2;
|
||||
var HEAD_TURN_RATE = 0.05;
|
||||
var PITCH_RANGE = 15.0;
|
||||
var YAW_RANGE = 35.0;
|
||||
|
||||
var firstPosition = { x: getRandomFloat(X_MIN, X_MAX), y: Y_PELVIS, z: getRandomFloat(Z_MIN, Z_MAX) };
|
||||
var targetPosition = { x: 0, y: 0, z: 0 };
|
||||
var targetOrientation = { x: 0, y: 0, z: 0, w: 0 };
|
||||
var currentOrientation = { x: 0, y: 0, z: 0, w: 0 };
|
||||
var targetHeadPitch = 0.0;
|
||||
var targetHeadYaw = 0.0;
|
||||
|
||||
var basePelvisHeight = 0.0;
|
||||
var pelvisOscillatorPosition = 0.0;
|
||||
var pelvisOscillatorVelocity = 0.0;
|
||||
|
||||
function clamp(val, min, max){
|
||||
return Math.max(min, Math.min(max, val))
|
||||
}
|
||||
|
||||
//Array of all valid bot numbers
|
||||
var validBotNumbers = [];
|
||||
|
||||
// right now we only use bot 63, since many other bots have messed up skeletons and LOD issues
|
||||
var botNumber = 63;//getRandomInt(0, 99);
|
||||
|
||||
var newFaceFilePrefix = "ron";
|
||||
|
||||
var newBodyFilePrefix = "bot" + botNumber;
|
||||
|
||||
// set the face model fst using the bot number
|
||||
// there is no need to change the body model - we're using the default
|
||||
Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newFaceFilePrefix + ".fst";
|
||||
Avatar.skeletonModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newBodyFilePrefix + "_a.fst";
|
||||
Avatar.billboardURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/billboards/bot" + botNumber + ".png";
|
||||
|
||||
Agent.isAvatar = true;
|
||||
Agent.isListeningToAudioStream = true;
|
||||
|
||||
// change the avatar's position to the random one
|
||||
Avatar.position = firstPosition;
|
||||
basePelvisHeight = firstPosition.y;
|
||||
printVector("New dancer, position = ", Avatar.position);
|
||||
|
||||
function loadSounds() {
|
||||
var sound_filenames = ["AB1.raw", "Anchorman2.raw", "B1.raw", "B1.raw", "Bale1.raw", "Bandcamp.raw",
|
||||
"Big1.raw", "Big2.raw", "Brian1.raw", "Buster1.raw", "CES1.raw", "CES2.raw", "CES3.raw", "CES4.raw",
|
||||
"Carrie1.raw", "Carrie3.raw", "Charlotte1.raw", "EN1.raw", "EN2.raw", "EN3.raw", "Eugene1.raw", "Francesco1.raw",
|
||||
"Italian1.raw", "Japanese1.raw", "Leigh1.raw", "Lucille1.raw", "Lucille2.raw", "MeanGirls.raw", "Murray2.raw",
|
||||
"Nigel1.raw", "PennyLane.raw", "Pitt1.raw", "Ricardo.raw", "SN.raw", "Sake1.raw", "Samantha1.raw", "Samantha2.raw",
|
||||
"Spicoli1.raw", "Supernatural.raw", "Swearengen1.raw", "TheDude.raw", "Tony.raw", "Triumph1.raw", "Uma1.raw",
|
||||
"Walken1.raw", "Walken2.raw", "Z1.raw", "Z2.raw"
|
||||
];
|
||||
|
||||
var footstep_filenames = ["FootstepW2Left-12db.wav", "FootstepW2Right-12db.wav", "FootstepW3Left-12db.wav", "FootstepW3Right-12db.wav",
|
||||
"FootstepW5Left-12db.wav", "FootstepW5Right-12db.wav"];
|
||||
|
||||
var SOUND_BASE_URL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Cocktail+Party+Snippets/Raws/";
|
||||
|
||||
var FOOTSTEP_BASE_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Footsteps/";
|
||||
|
||||
for (var i = 0; i < sound_filenames.length; i++) {
|
||||
sounds.push(new Sound(SOUND_BASE_URL + sound_filenames[i]));
|
||||
}
|
||||
|
||||
for (var i = 0; i < footstep_filenames.length; i++) {
|
||||
footstepSounds.push(new Sound(FOOTSTEP_BASE_URL + footstep_filenames[i]));
|
||||
}
|
||||
}
|
||||
|
||||
var sounds = [];
|
||||
var footstepSounds = [];
|
||||
loadSounds();
|
||||
|
||||
|
||||
function playRandomSound() {
|
||||
if (!Agent.isPlayingAvatarSound) {
|
||||
var whichSound = Math.floor((Math.random() * sounds.length));
|
||||
Agent.playAvatarSound(sounds[whichSound]);
|
||||
}
|
||||
}
|
||||
|
||||
function playRandomFootstepSound() {
|
||||
|
||||
var whichSound = Math.floor((Math.random() * footstepSounds.length));
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = Avatar.position;
|
||||
options.volume = 1.0;
|
||||
Audio.playSound(footstepSounds[whichSound], options);
|
||||
|
||||
}
|
||||
|
||||
// ************************************ Facial Animation **********************************
|
||||
var allBlendShapes = [];
|
||||
var targetBlendCoefficient = [];
|
||||
var currentBlendCoefficient = [];
|
||||
|
||||
//Blendshape constructor
|
||||
function addBlendshapeToPose(pose, shapeIndex, val) {
|
||||
var index = pose.blendShapes.length;
|
||||
pose.blendShapes[index] = {shapeIndex: shapeIndex, val: val };
|
||||
}
|
||||
//The mood of the avatar, determines face. 0 = happy, 1 = angry, 2 = sad.
|
||||
|
||||
//Randomly pick avatar mood. 80% happy, 10% mad 10% sad
|
||||
var randMood = Math.floor(Math.random() * 11);
|
||||
var avatarMood;
|
||||
if (randMood == 0) {
|
||||
avatarMood = 1;
|
||||
} else if (randMood == 2) {
|
||||
avatarMood = 2;
|
||||
} else {
|
||||
avatarMood = 0;
|
||||
}
|
||||
|
||||
var currentExpression = -1;
|
||||
//Face pose constructor
|
||||
var happyPoses = [];
|
||||
|
||||
happyPoses[0] = {blendShapes: []};
|
||||
addBlendshapeToPose(happyPoses[0], 28, 0.7); //MouthSmile_L
|
||||
addBlendshapeToPose(happyPoses[0], 29, 0.7); //MouthSmile_R
|
||||
|
||||
happyPoses[1] = {blendShapes: []};
|
||||
addBlendshapeToPose(happyPoses[1], 28, 1.0); //MouthSmile_L
|
||||
addBlendshapeToPose(happyPoses[1], 29, 1.0); //MouthSmile_R
|
||||
addBlendshapeToPose(happyPoses[1], 21, 0.2); //JawOpen
|
||||
|
||||
happyPoses[2] = {blendShapes: []};
|
||||
addBlendshapeToPose(happyPoses[2], 28, 1.0); //MouthSmile_L
|
||||
addBlendshapeToPose(happyPoses[2], 29, 1.0); //MouthSmile_R
|
||||
addBlendshapeToPose(happyPoses[2], 21, 0.5); //JawOpen
|
||||
addBlendshapeToPose(happyPoses[2], 46, 1.0); //CheekSquint_L
|
||||
addBlendshapeToPose(happyPoses[2], 47, 1.0); //CheekSquint_R
|
||||
addBlendshapeToPose(happyPoses[2], 17, 1.0); //BrowsU_L
|
||||
addBlendshapeToPose(happyPoses[2], 18, 1.0); //BrowsU_R
|
||||
|
||||
var angryPoses = [];
|
||||
|
||||
angryPoses[0] = {blendShapes: []};
|
||||
addBlendshapeToPose(angryPoses[0], 26, 0.6); //MouthFrown_L
|
||||
addBlendshapeToPose(angryPoses[0], 27, 0.6); //MouthFrown_R
|
||||
addBlendshapeToPose(angryPoses[0], 14, 0.6); //BrowsD_L
|
||||
addBlendshapeToPose(angryPoses[0], 15, 0.6); //BrowsD_R
|
||||
|
||||
angryPoses[1] = {blendShapes: []};
|
||||
addBlendshapeToPose(angryPoses[1], 26, 0.9); //MouthFrown_L
|
||||
addBlendshapeToPose(angryPoses[1], 27, 0.9); //MouthFrown_R
|
||||
addBlendshapeToPose(angryPoses[1], 14, 0.9); //BrowsD_L
|
||||
addBlendshapeToPose(angryPoses[1], 15, 0.9); //BrowsD_R
|
||||
|
||||
angryPoses[2] = {blendShapes: []};
|
||||
addBlendshapeToPose(angryPoses[2], 26, 1.0); //MouthFrown_L
|
||||
addBlendshapeToPose(angryPoses[2], 27, 1.0); //MouthFrown_R
|
||||
addBlendshapeToPose(angryPoses[2], 14, 1.0); //BrowsD_L
|
||||
addBlendshapeToPose(angryPoses[2], 15, 1.0); //BrowsD_R
|
||||
addBlendshapeToPose(angryPoses[2], 21, 0.5); //JawOpen
|
||||
addBlendshapeToPose(angryPoses[2], 46, 1.0); //CheekSquint_L
|
||||
addBlendshapeToPose(angryPoses[2], 47, 1.0); //CheekSquint_R
|
||||
|
||||
var sadPoses = [];
|
||||
|
||||
sadPoses[0] = {blendShapes: []};
|
||||
addBlendshapeToPose(sadPoses[0], 26, 0.6); //MouthFrown_L
|
||||
addBlendshapeToPose(sadPoses[0], 27, 0.6); //MouthFrown_R
|
||||
addBlendshapeToPose(sadPoses[0], 16, 0.2); //BrowsU_C
|
||||
addBlendshapeToPose(sadPoses[0], 2, 0.6); //EyeSquint_L
|
||||
addBlendshapeToPose(sadPoses[0], 3, 0.6); //EyeSquint_R
|
||||
|
||||
sadPoses[1] = {blendShapes: []};
|
||||
addBlendshapeToPose(sadPoses[1], 26, 0.9); //MouthFrown_L
|
||||
addBlendshapeToPose(sadPoses[1], 27, 0.9); //MouthFrown_R
|
||||
addBlendshapeToPose(sadPoses[1], 16, 0.6); //BrowsU_C
|
||||
addBlendshapeToPose(sadPoses[1], 2, 0.9); //EyeSquint_L
|
||||
addBlendshapeToPose(sadPoses[1], 3, 0.9); //EyeSquint_R
|
||||
|
||||
sadPoses[2] = {blendShapes: []};
|
||||
addBlendshapeToPose(sadPoses[2], 26, 1.0); //MouthFrown_L
|
||||
addBlendshapeToPose(sadPoses[2], 27, 1.0); //MouthFrown_R
|
||||
addBlendshapeToPose(sadPoses[2], 16, 0.1); //BrowsU_C
|
||||
addBlendshapeToPose(sadPoses[2], 2, 1.0); //EyeSquint_L
|
||||
addBlendshapeToPose(sadPoses[2], 3, 1.0); //EyeSquint_R
|
||||
addBlendshapeToPose(sadPoses[2], 21, 0.3); //JawOpen
|
||||
|
||||
var facePoses = [];
|
||||
facePoses[0] = happyPoses;
|
||||
facePoses[1] = angryPoses;
|
||||
facePoses[2] = sadPoses;
|
||||
|
||||
|
||||
function addBlendShape(s) {
|
||||
allBlendShapes[allBlendShapes.length] = s;
|
||||
}
|
||||
|
||||
//It is imperative that the following blendshapes are all present and are in the correct order
|
||||
addBlendShape("EyeBlink_L"); //0
|
||||
addBlendShape("EyeBlink_R"); //1
|
||||
addBlendShape("EyeSquint_L"); //2
|
||||
addBlendShape("EyeSquint_R"); //3
|
||||
addBlendShape("EyeDown_L"); //4
|
||||
addBlendShape("EyeDown_R"); //5
|
||||
addBlendShape("EyeIn_L"); //6
|
||||
addBlendShape("EyeIn_R"); //7
|
||||
addBlendShape("EyeOpen_L"); //8
|
||||
addBlendShape("EyeOpen_R"); //9
|
||||
addBlendShape("EyeOut_L"); //10
|
||||
addBlendShape("EyeOut_R"); //11
|
||||
addBlendShape("EyeUp_L"); //12
|
||||
addBlendShape("EyeUp_R"); //13
|
||||
addBlendShape("BrowsD_L"); //14
|
||||
addBlendShape("BrowsD_R"); //15
|
||||
addBlendShape("BrowsU_C"); //16
|
||||
addBlendShape("BrowsU_L"); //17
|
||||
addBlendShape("BrowsU_R"); //18
|
||||
addBlendShape("JawFwd"); //19
|
||||
addBlendShape("JawLeft"); //20
|
||||
addBlendShape("JawOpen"); //21
|
||||
addBlendShape("JawChew"); //22
|
||||
addBlendShape("JawRight"); //23
|
||||
addBlendShape("MouthLeft"); //24
|
||||
addBlendShape("MouthRight"); //25
|
||||
addBlendShape("MouthFrown_L"); //26
|
||||
addBlendShape("MouthFrown_R"); //27
|
||||
addBlendShape("MouthSmile_L"); //28
|
||||
addBlendShape("MouthSmile_R"); //29
|
||||
addBlendShape("MouthDimple_L"); //30
|
||||
addBlendShape("MouthDimple_R"); //31
|
||||
addBlendShape("LipsStretch_L"); //32
|
||||
addBlendShape("LipsStretch_R"); //33
|
||||
addBlendShape("LipsUpperClose"); //34
|
||||
addBlendShape("LipsLowerClose"); //35
|
||||
addBlendShape("LipsUpperUp"); //36
|
||||
addBlendShape("LipsLowerDown"); //37
|
||||
addBlendShape("LipsUpperOpen"); //38
|
||||
addBlendShape("LipsLowerOpen"); //39
|
||||
addBlendShape("LipsFunnel"); //40
|
||||
addBlendShape("LipsPucker"); //41
|
||||
addBlendShape("ChinLowerRaise"); //42
|
||||
addBlendShape("ChinUpperRaise"); //43
|
||||
addBlendShape("Sneer"); //44
|
||||
addBlendShape("Puff"); //45
|
||||
addBlendShape("CheekSquint_L"); //46
|
||||
addBlendShape("CheekSquint_R"); //47
|
||||
|
||||
for (var i = 0; i < allBlendShapes.length; i++) {
|
||||
targetBlendCoefficient[i] = 0;
|
||||
currentBlendCoefficient[i] = 0;
|
||||
}
|
||||
|
||||
function setRandomExpression() {
|
||||
|
||||
//Clear all expression data for current expression
|
||||
if (currentExpression != -1) {
|
||||
var expression = facePoses[avatarMood][currentExpression];
|
||||
for (var i = 0; i < expression.blendShapes.length; i++) {
|
||||
targetBlendCoefficient[expression.blendShapes[i].shapeIndex] = 0.0;
|
||||
}
|
||||
}
|
||||
//Get a new current expression
|
||||
currentExpression = Math.floor(Math.random() * facePoses[avatarMood].length);
|
||||
var expression = facePoses[avatarMood][currentExpression];
|
||||
for (var i = 0; i < expression.blendShapes.length; i++) {
|
||||
targetBlendCoefficient[expression.blendShapes[i].shapeIndex] = expression.blendShapes[i].val;
|
||||
}
|
||||
}
|
||||
|
||||
var expressionChangeSpeed = 0.1;
|
||||
function updateBlendShapes(deltaTime) {
|
||||
|
||||
for (var i = 0; i < allBlendShapes.length; i++) {
|
||||
currentBlendCoefficient[i] += (targetBlendCoefficient[i] - currentBlendCoefficient[i]) * expressionChangeSpeed;
|
||||
Avatar.setBlendshape(allBlendShapes[i], currentBlendCoefficient[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var BLINK_SPEED = 0.15;
|
||||
var CHANCE_TO_BLINK = 0.0025;
|
||||
var MAX_BLINK = 0.85;
|
||||
var blink = 0.0;
|
||||
var isBlinking = false;
|
||||
function updateBlinking(deltaTime) {
|
||||
if (isBlinking == false) {
|
||||
if (Math.random() < CHANCE_TO_BLINK) {
|
||||
isBlinking = true;
|
||||
} else {
|
||||
blink -= BLINK_SPEED;
|
||||
if (blink < 0.0) blink = 0.0;
|
||||
}
|
||||
} else {
|
||||
blink += BLINK_SPEED;
|
||||
if (blink > MAX_BLINK) {
|
||||
blink = MAX_BLINK;
|
||||
isBlinking = false;
|
||||
}
|
||||
}
|
||||
|
||||
currentBlendCoefficient[0] = blink;
|
||||
currentBlendCoefficient[1] = blink;
|
||||
targetBlendCoefficient[0] = blink;
|
||||
targetBlendCoefficient[1] = blink;
|
||||
}
|
||||
|
||||
// *************************************************************************************
|
||||
|
||||
//Procedural walk animation using two keyframes
|
||||
//We use a separate array for front and back joints
|
||||
//Pitch, yaw, and roll for the joints
|
||||
var rightAngles = [];
|
||||
var leftAngles = [];
|
||||
//for non mirrored joints such as the spine
|
||||
var middleAngles = [];
|
||||
|
||||
//Actual joint mappings
|
||||
var SHOULDER_JOINT_NUMBER = 15;
|
||||
var ELBOW_JOINT_NUMBER = 16;
|
||||
var JOINT_R_HIP = 1;
|
||||
var JOINT_R_KNEE = 2;
|
||||
var JOINT_L_HIP = 6;
|
||||
var JOINT_L_KNEE = 7;
|
||||
var JOINT_R_ARM = 15;
|
||||
var JOINT_R_FOREARM = 16;
|
||||
var JOINT_L_ARM = 39;
|
||||
var JOINT_L_FOREARM = 40;
|
||||
var JOINT_SPINE = 11;
|
||||
var JOINT_R_FOOT = 3;
|
||||
var JOINT_L_FOOT = 8;
|
||||
var JOINT_R_TOE = 4;
|
||||
var JOINT_L_TOE = 9;
|
||||
|
||||
// ******************************* Animation Is Defined Below *************************************
|
||||
|
||||
var NUM_FRAMES = 2;
|
||||
for (var i = 0; i < NUM_FRAMES; i++) {
|
||||
rightAngles[i] = [];
|
||||
leftAngles[i] = [];
|
||||
middleAngles[i] = [];
|
||||
}
|
||||
//Joint order for actual joint mappings, should be interleaved R,L,R,L,...S,S,S for R = right, L = left, S = single
|
||||
var JOINT_ORDER = [];
|
||||
//*** right / left joints ***
|
||||
var HIP = 0;
|
||||
JOINT_ORDER.push(JOINT_R_HIP);
|
||||
JOINT_ORDER.push(JOINT_L_HIP);
|
||||
var KNEE = 1;
|
||||
JOINT_ORDER.push(JOINT_R_KNEE);
|
||||
JOINT_ORDER.push(JOINT_L_KNEE);
|
||||
var ARM = 2;
|
||||
JOINT_ORDER.push(JOINT_R_ARM);
|
||||
JOINT_ORDER.push(JOINT_L_ARM);
|
||||
var FOREARM = 3;
|
||||
JOINT_ORDER.push(JOINT_R_FOREARM);
|
||||
JOINT_ORDER.push(JOINT_L_FOREARM);
|
||||
var FOOT = 4;
|
||||
JOINT_ORDER.push(JOINT_R_FOOT);
|
||||
JOINT_ORDER.push(JOINT_L_FOOT);
|
||||
var TOE = 5;
|
||||
JOINT_ORDER.push(JOINT_R_TOE);
|
||||
JOINT_ORDER.push(JOINT_L_TOE);
|
||||
//*** middle joints ***
|
||||
var SPINE = 0;
|
||||
JOINT_ORDER.push(JOINT_SPINE);
|
||||
|
||||
//We have to store the angles so we can invert yaw and roll when making the animation
|
||||
//symmetrical
|
||||
|
||||
//Front refers to leg, not arm.
|
||||
//Legs Extending
|
||||
rightAngles[0][HIP] = [30.0, 0.0, 8.0];
|
||||
rightAngles[0][KNEE] = [-15.0, 0.0, 0.0];
|
||||
rightAngles[0][ARM] = [85.0, -25.0, 0.0];
|
||||
rightAngles[0][FOREARM] = [0.0, 0.0, -15.0];
|
||||
rightAngles[0][FOOT] = [0.0, 0.0, 0.0];
|
||||
rightAngles[0][TOE] = [0.0, 0.0, 0.0];
|
||||
|
||||
leftAngles[0][HIP] = [-15, 0.0, 8.0];
|
||||
leftAngles[0][KNEE] = [-26, 0.0, 0.0];
|
||||
leftAngles[0][ARM] = [85.0, 20.0, 0.0];
|
||||
leftAngles[0][FOREARM] = [10.0, 0.0, -25.0];
|
||||
leftAngles[0][FOOT] = [-13.0, 0.0, 0.0];
|
||||
leftAngles[0][TOE] = [34.0, 0.0, 0.0];
|
||||
|
||||
middleAngles[0][SPINE] = [0.0, -15.0, 5.0];
|
||||
|
||||
//Legs Passing
|
||||
rightAngles[1][HIP] = [6.0, 0.0, 8.0];
|
||||
rightAngles[1][KNEE] = [-12.0, 0.0, 0.0];
|
||||
rightAngles[1][ARM] = [85.0, 0.0, 0.0];
|
||||
rightAngles[1][FOREARM] = [0.0, 0.0, -15.0];
|
||||
rightAngles[1][FOOT] = [6.0, -8.0, 0.0];
|
||||
rightAngles[1][TOE] = [0.0, 0.0, 0.0];
|
||||
|
||||
leftAngles[1][HIP] = [10.0, 0.0, 8.0];
|
||||
leftAngles[1][KNEE] = [-60.0, 0.0, 0.0];
|
||||
leftAngles[1][ARM] = [85.0, 0.0, 0.0];
|
||||
leftAngles[1][FOREARM] = [0.0, 0.0, -15.0];
|
||||
leftAngles[1][FOOT] = [0.0, 0.0, 0.0];
|
||||
leftAngles[1][TOE] = [0.0, 0.0, 0.0];
|
||||
|
||||
middleAngles[1][SPINE] = [0.0, 0.0, 0.0];
|
||||
|
||||
//Actual keyframes for the animation
|
||||
var walkKeyFrames = procAnimAPI.generateKeyframes(rightAngles, leftAngles, middleAngles, NUM_FRAMES);
|
||||
|
||||
// ******************************* Animation Is Defined Above *************************************
|
||||
|
||||
// ********************************** Standing Key Frame ******************************************
|
||||
//We don't have to do any mirroring or anything, since this is just a single pose.
|
||||
var rightQuats = [];
|
||||
var leftQuats = [];
|
||||
var middleQuats = [];
|
||||
|
||||
rightQuats[HIP] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 7.0);
|
||||
rightQuats[KNEE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
|
||||
rightQuats[ARM] = Quat.fromPitchYawRollDegrees(85.0, 0.0, 0.0);
|
||||
rightQuats[FOREARM] = Quat.fromPitchYawRollDegrees(0.0, 0.0, -10.0);
|
||||
rightQuats[FOOT] = Quat.fromPitchYawRollDegrees(0.0, -8.0, 0.0);
|
||||
rightQuats[TOE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
|
||||
|
||||
leftQuats[HIP] = Quat.fromPitchYawRollDegrees(0, 0.0, -7.0);
|
||||
leftQuats[KNEE] = Quat.fromPitchYawRollDegrees(0, 0.0, 0.0);
|
||||
leftQuats[ARM] = Quat.fromPitchYawRollDegrees(85.0, 0.0, 0.0);
|
||||
leftQuats[FOREARM] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 10.0);
|
||||
leftQuats[FOOT] = Quat.fromPitchYawRollDegrees(0.0, 8.0, 0.0);
|
||||
leftQuats[TOE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
|
||||
|
||||
middleQuats[SPINE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
|
||||
|
||||
var standingKeyFrame = new procAnimAPI.KeyFrame(rightQuats, leftQuats, middleQuats);
|
||||
|
||||
// ************************************************************************************************
|
||||
|
||||
|
||||
var currentFrame = 0;
|
||||
|
||||
var walkTime = 0.0;
|
||||
|
||||
var walkWheelRadius = 0.5;
|
||||
var walkWheelRate = 2.0 * 3.141592 * walkWheelRadius / 8.0;
|
||||
|
||||
var avatarAcceleration = 0.75;
|
||||
var avatarVelocity = 0.0;
|
||||
var avatarMaxVelocity = 1.4;
|
||||
|
||||
function handleAnimation(deltaTime) {
|
||||
|
||||
updateBlinking(deltaTime);
|
||||
updateBlendShapes(deltaTime);
|
||||
|
||||
if (Math.random() < 0.01) {
|
||||
setRandomExpression();
|
||||
}
|
||||
|
||||
if (avatarVelocity == 0.0) {
|
||||
walkTime = 0.0;
|
||||
currentFrame = 0;
|
||||
} else {
|
||||
walkTime += avatarVelocity * deltaTime;
|
||||
if (walkTime > walkWheelRate) {
|
||||
walkTime = 0.0;
|
||||
currentFrame++;
|
||||
if (currentFrame % 2 == 1) {
|
||||
playRandomFootstepSound();
|
||||
}
|
||||
if (currentFrame > 3) {
|
||||
currentFrame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var frame = walkKeyFrames[currentFrame];
|
||||
|
||||
var walkInterp = walkTime / walkWheelRate;
|
||||
var animInterp = avatarVelocity / (avatarMaxVelocity / 1.3);
|
||||
if (animInterp > 1.0) animInterp = 1.0;
|
||||
|
||||
for (var i = 0; i < JOINT_ORDER.length; i++) {
|
||||
var walkJoint = procAnimAPI.deCasteljau(frame.rotations[i], frame.nextFrame.rotations[i], frame.controlPoints[i][0], frame.controlPoints[i][1], walkInterp);
|
||||
var standJoint = standingKeyFrame.rotations[i];
|
||||
var finalJoint = Quat.mix(standJoint, walkJoint, animInterp);
|
||||
Avatar.setJointData(JOINT_ORDER[i], finalJoint);
|
||||
}
|
||||
}
|
||||
|
||||
function jumpWithLoudness(deltaTime) {
|
||||
// potentially change pelvis height depending on trailing average loudness
|
||||
|
||||
pelvisOscillatorVelocity += deltaTime * Agent.lastReceivedAudioLoudness * 700.0 ;
|
||||
|
||||
pelvisOscillatorVelocity -= pelvisOscillatorPosition * 0.75;
|
||||
pelvisOscillatorVelocity *= 0.97;
|
||||
pelvisOscillatorPosition += deltaTime * pelvisOscillatorVelocity;
|
||||
Avatar.headPitch = pelvisOscillatorPosition * 60.0;
|
||||
|
||||
var pelvisPosition = Avatar.position;
|
||||
pelvisPosition.y = (Y_PELVIS - 0.35) + pelvisOscillatorPosition;
|
||||
|
||||
if (pelvisPosition.y < Y_PELVIS) {
|
||||
pelvisPosition.y = Y_PELVIS;
|
||||
} else if (pelvisPosition.y > Y_PELVIS + 1.0) {
|
||||
pelvisPosition.y = Y_PELVIS + 1.0;
|
||||
}
|
||||
|
||||
Avatar.position = pelvisPosition;
|
||||
}
|
||||
|
||||
var forcedMove = false;
|
||||
|
||||
var wasMovingLastFrame = false;
|
||||
|
||||
function handleHeadTurn() {
|
||||
if (!isTurningHead && (Math.random() < CHANCE_OF_HEAD_TURNING)) {
|
||||
targetHeadPitch = getRandomFloat(-PITCH_RANGE, PITCH_RANGE);
|
||||
targetHeadYaw = getRandomFloat(-YAW_RANGE, YAW_RANGE);
|
||||
isTurningHead = true;
|
||||
} else {
|
||||
Avatar.headPitch = Avatar.headPitch + (targetHeadPitch - Avatar.headPitch) * HEAD_TURN_RATE;
|
||||
Avatar.headYaw = Avatar.headYaw + (targetHeadYaw - Avatar.headYaw) * HEAD_TURN_RATE;
|
||||
if (Math.abs(Avatar.headPitch - targetHeadPitch) < STOP_TOLERANCE &&
|
||||
Math.abs(Avatar.headYaw - targetHeadYaw) < STOP_TOLERANCE) {
|
||||
isTurningHead = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopWalking() {
|
||||
avatarVelocity = 0.0;
|
||||
isMoving = false;
|
||||
}
|
||||
|
||||
var MAX_ATTEMPTS = 40;
|
||||
function handleWalking(deltaTime) {
|
||||
|
||||
if (forcedMove || (!isMoving && Math.random() < CHANCE_OF_MOVING)) {
|
||||
// Set new target location
|
||||
|
||||
var moveRange;
|
||||
if (Math.random() < CHANCE_OF_BIG_MOVE) {
|
||||
moveRange = MOVE_RANGE_BIG;
|
||||
} else {
|
||||
moveRange = MOVE_RANGE_SMALL;
|
||||
}
|
||||
|
||||
//Keep trying new orientations if the desired target location is out of bounds
|
||||
var attempts = 0;
|
||||
do {
|
||||
targetOrientation = Quat.multiply(Avatar.orientation, Quat.angleAxis(getRandomFloat(-TURN_RANGE, TURN_RANGE), { x:0, y:1, z:0 }));
|
||||
var front = Quat.getFront(targetOrientation);
|
||||
|
||||
targetPosition = Vec3.sum(Avatar.position, Vec3.multiply(front, getRandomFloat(0.0, moveRange)));
|
||||
}
|
||||
while ((targetPosition.x < X_MIN || targetPosition.x > X_MAX || targetPosition.z < Z_MIN || targetPosition.z > Z_MAX)
|
||||
&& attempts < MAX_ATTEMPTS);
|
||||
|
||||
targetPosition.x = clamp(targetPosition.x, X_MIN, X_MAX);
|
||||
targetPosition.z = clamp(targetPosition.z, Z_MIN, Z_MAX);
|
||||
targetPosition.y = Y_PELVIS;
|
||||
|
||||
wasMovingLastFrame = true;
|
||||
isMoving = true;
|
||||
forcedMove = false;
|
||||
} else if (isMoving) {
|
||||
|
||||
var targetVector = Vec3.subtract(targetPosition, Avatar.position);
|
||||
var distance = Vec3.length(targetVector);
|
||||
if (distance <= avatarVelocity * deltaTime) {
|
||||
Avatar.position = targetPosition;
|
||||
stopWalking();
|
||||
} else {
|
||||
var direction = Vec3.normalize(targetVector);
|
||||
//Figure out if we should be slowing down
|
||||
var t = avatarVelocity / avatarAcceleration;
|
||||
var d = (avatarVelocity / 2.0) * t;
|
||||
if (distance < d) {
|
||||
avatarVelocity -= avatarAcceleration * deltaTime;
|
||||
if (avatarVelocity <= 0) {
|
||||
stopWalking();
|
||||
}
|
||||
} else {
|
||||
avatarVelocity += avatarAcceleration * deltaTime;
|
||||
if (avatarVelocity > avatarMaxVelocity) avatarVelocity = avatarMaxVelocity;
|
||||
}
|
||||
Avatar.position = Vec3.sum(Avatar.position, Vec3.multiply(direction, avatarVelocity * deltaTime));
|
||||
Avatar.orientation = Quat.mix(Avatar.orientation, targetOrientation, TURN_RATE);
|
||||
|
||||
wasMovingLastFrame = true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleTalking() {
|
||||
if (Math.random() < CHANCE_OF_SOUND) {
|
||||
playRandomSound();
|
||||
}
|
||||
}
|
||||
|
||||
function changePelvisHeight(newHeight) {
|
||||
var newPosition = Avatar.position;
|
||||
newPosition.y = newHeight;
|
||||
Avatar.position = newPosition;
|
||||
}
|
||||
|
||||
function updateBehavior(deltaTime) {
|
||||
|
||||
if (AvatarList.containsAvatarWithDisplayName("mrdj")) {
|
||||
if (wasMovingLastFrame) {
|
||||
isMoving = false;
|
||||
}
|
||||
|
||||
// we have a DJ, shouldn't we be dancing?
|
||||
jumpWithLoudness(deltaTime);
|
||||
} else {
|
||||
|
||||
// no DJ, let's just chill on the dancefloor - randomly walking and talking
|
||||
handleHeadTurn();
|
||||
handleAnimation(deltaTime);
|
||||
handleWalking(deltaTime);
|
||||
handleTalking();
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(updateBehavior);
|
144
examples/proceduralAnimationAPI.js
Normal file
144
examples/proceduralAnimationAPI.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// proceduralAnimation.js
|
||||
// hifi
|
||||
//
|
||||
// Created by Ben Arnold on 7/29/14.
|
||||
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This is a Procedural Animation API for creating procedural animations in JS.
|
||||
// To include it in your JS files, simply use the following line at the top:
|
||||
// Script.include("proceduralAnimation.js");
|
||||
|
||||
// You can see a usage example in proceduralBot.js
|
||||
// The current implementation is quite simple. If you would like a feature
|
||||
// to be added or expanded, you can contact Ben at brb555@vols.utk.edu
|
||||
|
||||
ProcAnimAPI = function() {
|
||||
|
||||
// generateKeyFrames(rightAngles, leftAngles, middleAngles, numFrames)
|
||||
//
|
||||
// Parameters:
|
||||
// rightAngles - An array of tuples. The angles in degrees for the joints
|
||||
// on the right side of the body
|
||||
// leftAngles - An array of tuples. The angles in degrees for the joints
|
||||
// on the left side of the body
|
||||
// middleAngles - An array of tuples. The angles in degrees for the joints
|
||||
// on the left side of the body
|
||||
// numFrames - The number of frames in the animation, before mirroring.
|
||||
// for a 4 frame walk animation, simply supply 2 frames
|
||||
// and generateKeyFrames will return 4 frames.
|
||||
//
|
||||
// Return Value:
|
||||
// Returns an array of KeyFrames. Each KeyFrame has an array of quaternions
|
||||
// for each of the joints, generated from the input angles. They will be ordered
|
||||
// R,L,R,L,...M,M,M,M where R ~ rightAngles, L ~ leftAngles, M ~ middlesAngles.
|
||||
// The size of the returned array will be numFrames * 2
|
||||
this.generateKeyframes = function(rightAngles, leftAngles, middleAngles, numFrames) {
|
||||
|
||||
if (rightAngles.length != leftAngles.length) {
|
||||
print("ERROR: generateKeyFrames(...) rightAngles and leftAngles must have equal length.");
|
||||
}
|
||||
|
||||
//for mirrored joints, such as the arms or legs
|
||||
var rightQuats = [];
|
||||
var leftQuats = [];
|
||||
//for non mirrored joints such as the spine
|
||||
var middleQuats = [];
|
||||
|
||||
for (var i = 0; i < numFrames; i++) {
|
||||
rightQuats[i] = [];
|
||||
leftQuats[i] = [];
|
||||
middleQuats[i] = [];
|
||||
}
|
||||
|
||||
var finalKeyFrames = [];
|
||||
//Generate quaternions
|
||||
for (var i = 0; i < rightAngles.length; i++) {
|
||||
for (var j = 0; j < rightAngles[i].length; j++) {
|
||||
rightQuats[i][j] = Quat.fromPitchYawRollDegrees(rightAngles[i][j][0], rightAngles[i][j][1], rightAngles[i][j][2]);
|
||||
leftQuats[i][j] = Quat.fromPitchYawRollDegrees(leftAngles[i][j][0], -leftAngles[i][j][1], -leftAngles[i][j][2]);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < middleAngles.length; i++) {
|
||||
for (var j = 0; j < middleAngles[i].length; j++) {
|
||||
middleQuats[i][j] = Quat.fromPitchYawRollDegrees(middleAngles[i][j][0], middleAngles[i][j][1], middleAngles[i][j][2]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < numFrames; i++) {
|
||||
finalKeyFrames[i] = new this.KeyFrame(rightQuats[i], leftQuats[i], middleQuats[i]);
|
||||
}
|
||||
|
||||
//Generate mirrored quaternions for the other half of the animation
|
||||
for (var i = 0; i < rightAngles.length; i++) {
|
||||
for (var j = 0; j < rightAngles[i].length; j++) {
|
||||
rightQuats[i][j] = Quat.fromPitchYawRollDegrees(rightAngles[i][j][0], -rightAngles[i][j][1], -rightAngles[i][j][2]);
|
||||
leftQuats[i][j] = Quat.fromPitchYawRollDegrees(leftAngles[i][j][0], leftAngles[i][j][1], leftAngles[i][j][2]);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < middleAngles.length; i++) {
|
||||
for (var j = 0; j < middleAngles[i].length; j++) {
|
||||
middleQuats[i][j] = Quat.fromPitchYawRollDegrees(-middleAngles[i][j][0], -middleAngles[i][j][1], -middleAngles[i][j][2]);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < numFrames; i++) {
|
||||
finalKeyFrames[numFrames + i] = new this.KeyFrame(leftQuats[i], rightQuats[i], middleQuats[i]);
|
||||
}
|
||||
|
||||
//Generate control points
|
||||
this.computeBezierControlPoints(finalKeyFrames);
|
||||
|
||||
return finalKeyFrames;
|
||||
};
|
||||
|
||||
//Computes 2 controlPoints to each keyframe to be used in the bezier evaluation.
|
||||
//Technique is described at: //https://www.cs.tcd.ie/publications/tech-reports/reports.94/TCD-CS-94-18.pdf
|
||||
this.computeBezierControlPoints = function(keyFrames) {
|
||||
//Hook up pointers to the next keyframe
|
||||
for (var i = 0; i < keyFrames.length - 1; i++) {
|
||||
keyFrames[i].nextFrame = keyFrames[i+1];
|
||||
}
|
||||
keyFrames[keyFrames.length-1].nextFrame = keyFrames[0];
|
||||
|
||||
//Set up all C1
|
||||
for (var i = 0; i < keyFrames.length; i++) {
|
||||
keyFrames[i].nextFrame.controlPoints = [];
|
||||
for (var j = 0; j < keyFrames[i].rotations.length; j++) {
|
||||
keyFrames[i].nextFrame.controlPoints[j] = [];
|
||||
var R = Quat.slerp(keyFrames[i].rotations[j], keyFrames[i].nextFrame.rotations[j], 2.0);
|
||||
var T = Quat.slerp(R, keyFrames[i].nextFrame.nextFrame.rotations[j], 0.5);
|
||||
keyFrames[i].nextFrame.controlPoints[j][0] = Quat.slerp(keyFrames[i].nextFrame.rotations[j], T, 0.33333);
|
||||
}
|
||||
}
|
||||
//Set up all C2
|
||||
for (var i = 0; i < keyFrames.length; i++) {
|
||||
for (var j = 0; j < keyFrames[i].rotations.length; j++) {
|
||||
keyFrames[i].controlPoints[j][1] = Quat.slerp(keyFrames[i].nextFrame.rotations[j], keyFrames[i].nextFrame.controlPoints[j][0], -1.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Animation KeyFrame constructor. rightJoints and leftJoints must be the same size
|
||||
this.KeyFrame = function(rightJoints, leftJoints, middleJoints) {
|
||||
this.rotations = [];
|
||||
|
||||
for (var i = 0; i < rightJoints.length; i++) {
|
||||
this.rotations[this.rotations.length] = rightJoints[i];
|
||||
this.rotations[this.rotations.length] = leftJoints[i];
|
||||
}
|
||||
for (var i = 0; i < middleJoints.length; i++) {
|
||||
this.rotations[this.rotations.length] = middleJoints[i];
|
||||
}
|
||||
};
|
||||
|
||||
// DeCasteljau evaluation to evaluate the bezier curve.
|
||||
// This is a very natural looking interpolation
|
||||
this.deCasteljau = function(k1, k2, c1, c2, f) {
|
||||
var a = Quat.slerp(k1, c1, f);
|
||||
var b = Quat.slerp(c1, c2, f);
|
||||
var c = Quat.slerp(c2, k2, f);
|
||||
var d = Quat.slerp(a, b, f);
|
||||
var e = Quat.slerp(b, c, f);
|
||||
return Quat.slerp(d, e, f);
|
||||
};
|
||||
}
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
#include "InterfaceVersion.h"
|
||||
#include "Menu.h"
|
||||
#include "ModelUploader.h"
|
||||
#include "PaymentManager.h"
|
||||
#include "Util.h"
|
||||
#include "devices/MIDIManager.h"
|
||||
#include "devices/OculusManager.h"
|
||||
|
@ -233,9 +234,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));
|
||||
|
||||
audioThread->start();
|
||||
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
|
||||
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
|
||||
|
||||
// hookup VoxelEditSender to PaymentManager so we can pay for octree edits
|
||||
const PaymentManager& paymentManager = PaymentManager::getInstance();
|
||||
connect(&_voxelEditSender, &VoxelEditPacketSender::octreePaymentRequired,
|
||||
&paymentManager, &PaymentManager::sendSignedPayment);
|
||||
|
||||
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
|
||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||
|
@ -3791,6 +3800,38 @@ void Application::uploadAttachment() {
|
|||
uploadModel(ATTACHMENT_MODEL);
|
||||
}
|
||||
|
||||
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
|
||||
|
||||
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
|
||||
const QString VOXEL_SETTINGS_KEY = "voxels";
|
||||
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
|
||||
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
|
||||
const QString VOXEL_WALLET_UUID = "voxel-wallet";
|
||||
|
||||
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
|
||||
|
||||
qint64 satoshisPerVoxel = 0;
|
||||
qint64 satoshisPerMeterCubed = 0;
|
||||
QUuid voxelWalletUUID;
|
||||
|
||||
if (!domainSettingsObject.isEmpty()) {
|
||||
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
|
||||
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
|
||||
|
||||
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
|
||||
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
|
||||
|
||||
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
|
||||
}
|
||||
|
||||
qDebug() << "Voxel costs are" << satoshisPerVoxel << "per voxel and" << satoshisPerMeterCubed << "per meter cubed";
|
||||
qDebug() << "Destination wallet UUID for voxel payments is" << voxelWalletUUID;
|
||||
|
||||
_voxelEditSender.setSatoshisPerVoxel(satoshisPerVoxel);
|
||||
_voxelEditSender.setSatoshisPerMeterCubed(satoshisPerMeterCubed);
|
||||
_voxelEditSender.setDestinationWalletUUID(voxelWalletUUID);
|
||||
}
|
||||
|
||||
QString Application::getPreviousScriptLocation() {
|
||||
QString suggestedName;
|
||||
if (_previousScriptLocation.isEmpty()) {
|
||||
|
|
|
@ -342,6 +342,8 @@ public slots:
|
|||
void uploadAttachment();
|
||||
|
||||
void bumpSettings() { ++_numChangedSettings; }
|
||||
|
||||
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
|
||||
|
||||
private slots:
|
||||
void timer();
|
||||
|
|
48
interface/src/PaymentManager.cpp
Normal file
48
interface/src/PaymentManager.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// PaymentManager.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-07-30.
|
||||
// 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/QDateTime>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "SignedWalletTransaction.h"
|
||||
|
||||
#include "PaymentManager.h"
|
||||
|
||||
PaymentManager& PaymentManager::getInstance() {
|
||||
static PaymentManager sharedInstance;
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
void PaymentManager::sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID) {
|
||||
|
||||
// setup a signed wallet transaction
|
||||
const qint64 DEFAULT_TRANSACTION_EXPIRY_SECONDS = 60;
|
||||
qint64 currentTimestamp = QDateTime::currentDateTimeUtc().toTime_t();
|
||||
SignedWalletTransaction newTransaction(destinationWalletUUID, satoshiAmount,
|
||||
currentTimestamp, DEFAULT_TRANSACTION_EXPIRY_SECONDS);
|
||||
|
||||
// send the signed transaction to the redeeming node
|
||||
QByteArray transactionByteArray = byteArrayWithPopulatedHeader(PacketTypeSignedTransactionPayment);
|
||||
|
||||
// append the binary message and the signed message digest
|
||||
transactionByteArray.append(newTransaction.binaryMessage());
|
||||
transactionByteArray.append(newTransaction.signedMessageDigest());
|
||||
|
||||
qDebug() << "Paying" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID;
|
||||
|
||||
// use the NodeList to send that to the right node
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->writeDatagram(transactionByteArray, nodeList->nodeWithUUID(nodeUUID));
|
||||
}
|
25
interface/src/PaymentManager.h
Normal file
25
interface/src/PaymentManager.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// PaymentManager.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-07-30.
|
||||
// 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_PaymentManager_h
|
||||
#define hifi_PaymentManager_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class PaymentManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static PaymentManager& getInstance();
|
||||
public slots:
|
||||
void sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID);
|
||||
};
|
||||
|
||||
#endif // hifi_PaymentManager_h
|
|
@ -32,7 +32,7 @@ SignedWalletTransaction::SignedWalletTransaction(const QUuid& destinationUUID, q
|
|||
|
||||
}
|
||||
|
||||
QByteArray SignedWalletTransaction::hexMessage() {
|
||||
QByteArray SignedWalletTransaction::binaryMessage() {
|
||||
// build the message using the components of this transaction
|
||||
|
||||
// UUID, source UUID, destination UUID, message timestamp, expiry delta, amount
|
||||
|
@ -49,7 +49,11 @@ QByteArray SignedWalletTransaction::hexMessage() {
|
|||
|
||||
messageBinary.append(reinterpret_cast<const char*>(&_amount), sizeof(_amount));
|
||||
|
||||
return messageBinary.toHex();
|
||||
return messageBinary;
|
||||
}
|
||||
|
||||
QByteArray SignedWalletTransaction::hexMessage() {
|
||||
return binaryMessage().toHex();
|
||||
}
|
||||
|
||||
QByteArray SignedWalletTransaction::messageDigest() {
|
||||
|
|
|
@ -19,6 +19,7 @@ class SignedWalletTransaction : public WalletTransaction {
|
|||
public:
|
||||
SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount, qint64 messageTimestamp, qint64 expiryDelta);
|
||||
|
||||
QByteArray binaryMessage();
|
||||
QByteArray hexMessage();
|
||||
QByteArray messageDigest();
|
||||
QByteArray signedMessageDigest();
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -120,6 +120,8 @@ class AvatarData : public QObject {
|
|||
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
|
||||
Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation)
|
||||
Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch)
|
||||
Q_PROPERTY(float headYaw READ getHeadYaw WRITE setHeadYaw)
|
||||
Q_PROPERTY(float headRoll READ getHeadRoll WRITE setHeadRoll)
|
||||
|
||||
Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness)
|
||||
Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness)
|
||||
|
@ -171,7 +173,13 @@ public:
|
|||
|
||||
// access to Head().set/getMousePitch (degrees)
|
||||
float getHeadPitch() const { return _headData->getBasePitch(); }
|
||||
void setHeadPitch(float value) { _headData->setBasePitch(value); };
|
||||
void setHeadPitch(float value) { _headData->setBasePitch(value); }
|
||||
|
||||
float getHeadYaw() const { return _headData->getBaseYaw(); }
|
||||
void setHeadYaw(float value) { _headData->setBaseYaw(value); }
|
||||
|
||||
float getHeadRoll() const { return _headData->getBaseRoll(); }
|
||||
void setHeadRoll(float value) { _headData->setBaseRoll(value); }
|
||||
|
||||
// access to Head().set/getAverageLoudness
|
||||
float getAudioLoudness() const { return _headData->getAudioLoudness(); }
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ const char* HTTPConnection::StatusCode200 = "200 OK";
|
|||
const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
|
||||
const char* HTTPConnection::StatusCode302 = "302 Found";
|
||||
const char* HTTPConnection::StatusCode400 = "400 Bad Request";
|
||||
const char* HTTPConnection::StatusCode401 = "401 Unauthorized";
|
||||
const char* HTTPConnection::StatusCode404 = "404 Not Found";
|
||||
const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1";
|
||||
|
||||
|
@ -42,7 +43,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
|
||||
HTTPConnection::~HTTPConnection() {
|
||||
// log the destruction
|
||||
if (_socket->error() != QAbstractSocket::UnknownSocketError) {
|
||||
if (_socket->error() != QAbstractSocket::UnknownSocketError
|
||||
&& _socket->error() != QAbstractSocket::RemoteHostClosedError) {
|
||||
qDebug() << _socket->errorString() << "-" << _socket->error();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ public:
|
|||
static const char* StatusCode301;
|
||||
static const char* StatusCode302;
|
||||
static const char* StatusCode400;
|
||||
static const char* StatusCode401;
|
||||
static const char* StatusCode404;
|
||||
static const char* DefaultContentType;
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
#include <QtCore/QMap>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QHttpMultiPart>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QHttpMultiPart>
|
||||
|
||||
#include "NodeList.h"
|
||||
#include "PacketHeaders.h"
|
||||
|
@ -62,6 +62,8 @@ AccountManager::AccountManager() :
|
|||
|
||||
qRegisterMetaType<QNetworkAccessManager::Operation>("QNetworkAccessManager::Operation");
|
||||
qRegisterMetaType<JSONCallbackParameters>("JSONCallbackParameters");
|
||||
|
||||
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
|
||||
|
||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||
}
|
||||
|
@ -142,90 +144,113 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan
|
|||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "invokedRequest",
|
||||
Q_ARG(const QString&, path),
|
||||
Q_ARG(bool, true),
|
||||
Q_ARG(QNetworkAccessManager::Operation, operation),
|
||||
Q_ARG(const JSONCallbackParameters&, callbackParams),
|
||||
Q_ARG(const QByteArray&, dataByteArray),
|
||||
Q_ARG(QHttpMultiPart*, dataMultiPart));
|
||||
}
|
||||
|
||||
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
|
||||
void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
|
||||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "invokedRequest",
|
||||
Q_ARG(const QString&, path),
|
||||
Q_ARG(bool, false),
|
||||
Q_ARG(QNetworkAccessManager::Operation, operation),
|
||||
Q_ARG(const JSONCallbackParameters&, callbackParams),
|
||||
Q_ARG(const QByteArray&, dataByteArray),
|
||||
Q_ARG(QHttpMultiPart*, dataMultiPart));
|
||||
}
|
||||
|
||||
void AccountManager::invokedRequest(const QString& path,
|
||||
bool requiresAuthentication,
|
||||
QNetworkAccessManager::Operation operation,
|
||||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
|
||||
|
||||
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
if (hasValidAccessToken()) {
|
||||
QNetworkRequest authenticatedRequest;
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
}
|
||||
|
||||
if (requiresAuthentication) {
|
||||
if (hasValidAccessToken()) {
|
||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
qDebug() << "No valid access token present. Bailing on authenticated invoked request.";
|
||||
return;
|
||||
}
|
||||
|
||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||
|
||||
authenticatedRequest.setUrl(requestURL);
|
||||
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qDebug() << "Making an authenticated request to" << qPrintable(requestURL.toString());
|
||||
|
||||
if (!dataByteArray.isEmpty()) {
|
||||
qDebug() << "The POST/PUT body -" << QString(dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qDebug() << "Making a request to" << qPrintable(requestURL.toString());
|
||||
|
||||
if (!dataByteArray.isEmpty()) {
|
||||
qDebug() << "The POST/PUT body -" << QString(dataByteArray);
|
||||
}
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
networkReply = networkAccessManager.get(authenticatedRequest);
|
||||
break;
|
||||
case QNetworkAccessManager::PostOperation:
|
||||
case QNetworkAccessManager::PutOperation:
|
||||
if (dataMultiPart) {
|
||||
if (operation == QNetworkAccessManager::PostOperation) {
|
||||
networkReply = networkAccessManager.post(authenticatedRequest, dataMultiPart);
|
||||
} else {
|
||||
networkReply = networkAccessManager.put(authenticatedRequest, dataMultiPart);
|
||||
}
|
||||
dataMultiPart->setParent(networkReply);
|
||||
}
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
networkReply = networkAccessManager.get(networkRequest);
|
||||
break;
|
||||
case QNetworkAccessManager::PostOperation:
|
||||
case QNetworkAccessManager::PutOperation:
|
||||
if (dataMultiPart) {
|
||||
if (operation == QNetworkAccessManager::PostOperation) {
|
||||
networkReply = networkAccessManager.post(networkRequest, dataMultiPart);
|
||||
} else {
|
||||
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
if (operation == QNetworkAccessManager::PostOperation) {
|
||||
networkReply = networkAccessManager.post(authenticatedRequest, dataByteArray);
|
||||
} else {
|
||||
networkReply = networkAccessManager.put(authenticatedRequest, dataByteArray);
|
||||
}
|
||||
networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
|
||||
}
|
||||
|
||||
break;
|
||||
case QNetworkAccessManager::DeleteOperation:
|
||||
networkReply = networkAccessManager.sendCustomRequest(authenticatedRequest, "DELETE");
|
||||
break;
|
||||
default:
|
||||
// other methods not yet handled
|
||||
break;
|
||||
}
|
||||
|
||||
if (networkReply) {
|
||||
if (!callbackParams.isEmpty()) {
|
||||
// if we have information for a callback, insert the callbackParams into our local map
|
||||
_pendingCallbackMap.insert(networkReply, callbackParams);
|
||||
|
||||
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
|
||||
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
|
||||
callbackParams.updateSlot.toStdString().c_str());
|
||||
dataMultiPart->setParent(networkReply);
|
||||
} else {
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
if (operation == QNetworkAccessManager::PostOperation) {
|
||||
networkReply = networkAccessManager.post(networkRequest, dataByteArray);
|
||||
} else {
|
||||
networkReply = networkAccessManager.put(networkRequest, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
// if we ended up firing of a request, hook up to it now
|
||||
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
|
||||
|
||||
break;
|
||||
case QNetworkAccessManager::DeleteOperation:
|
||||
networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE");
|
||||
break;
|
||||
default:
|
||||
// other methods not yet handled
|
||||
break;
|
||||
}
|
||||
|
||||
if (networkReply) {
|
||||
if (!callbackParams.isEmpty()) {
|
||||
// if we have information for a callback, insert the callbackParams into our local map
|
||||
_pendingCallbackMap.insert(networkReply, callbackParams);
|
||||
|
||||
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
|
||||
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
|
||||
callbackParams.updateSlot.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// if we ended up firing of a request, hook up to it now
|
||||
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,6 +301,7 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
|||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qDebug() << "Received error response from data-server that has no matching callback.";
|
||||
qDebug() << "Error" << requestReply->error() << "-" << requestReply->errorString();
|
||||
qDebug() << requestReply->readAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,12 @@ public:
|
|||
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
|
||||
const QByteArray& dataByteArray = QByteArray(),
|
||||
QHttpMultiPart* dataMultiPart = NULL);
|
||||
|
||||
void unauthenticatedRequest(const QString& path,
|
||||
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
|
||||
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
|
||||
const QByteArray& dataByteArray = QByteArray(),
|
||||
QHttpMultiPart* dataMultiPart = NULL);
|
||||
|
||||
const QUrl& getAuthURL() const { return _authURL; }
|
||||
void setAuthURL(const QUrl& authURL);
|
||||
|
@ -88,7 +94,9 @@ private:
|
|||
void passSuccessToCallback(QNetworkReply* reply);
|
||||
void passErrorToCallback(QNetworkReply* reply);
|
||||
|
||||
Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
|
||||
Q_INVOKABLE void invokedRequest(const QString& path,
|
||||
bool requiresAuthentication,
|
||||
QNetworkAccessManager::Operation operation,
|
||||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart);
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include "Assignment.h"
|
||||
#include "NodeList.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "UserActivityLogger.h"
|
||||
|
@ -21,7 +26,9 @@ DomainHandler::DomainHandler(QObject* parent) :
|
|||
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
|
||||
_assignmentUUID(),
|
||||
_isConnected(false),
|
||||
_handshakeTimer(NULL)
|
||||
_handshakeTimer(NULL),
|
||||
_settingsObject(),
|
||||
_failedSettingsRequests(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -37,8 +44,14 @@ void DomainHandler::clearConnectionInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainHandler::clearSettings() {
|
||||
_settingsObject = QJsonObject();
|
||||
_failedSettingsRequests = 0;
|
||||
}
|
||||
|
||||
void DomainHandler::reset() {
|
||||
clearConnectionInfo();
|
||||
clearSettings();
|
||||
_hostname = QString();
|
||||
_sockAddr.setAddress(QHostAddress::Null);
|
||||
}
|
||||
|
@ -109,10 +122,64 @@ void DomainHandler::setIsConnected(bool isConnected) {
|
|||
|
||||
if (_isConnected) {
|
||||
emit connectedToDomain(_hostname);
|
||||
|
||||
// we've connected to new domain - time to ask it for global settings
|
||||
requestDomainSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainHandler::requestDomainSettings() const {
|
||||
if (_settingsObject.isEmpty()) {
|
||||
// setup the URL required to grab settings JSON
|
||||
QUrl settingsJSONURL;
|
||||
settingsJSONURL.setScheme("http");
|
||||
settingsJSONURL.setHost(_hostname);
|
||||
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
|
||||
settingsJSONURL.setPath("/settings.json");
|
||||
Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType());
|
||||
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
|
||||
|
||||
qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString();
|
||||
|
||||
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL));
|
||||
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
|
||||
}
|
||||
}
|
||||
|
||||
const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
|
||||
|
||||
void DomainHandler::settingsRequestFinished() {
|
||||
QNetworkReply* settingsReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) {
|
||||
// parse the JSON to a QJsonObject and save it
|
||||
_settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object();
|
||||
|
||||
qDebug() << "Received domain settings.";
|
||||
emit settingsReceived(_settingsObject);
|
||||
|
||||
// reset failed settings requests to 0, we got them
|
||||
_failedSettingsRequests = 0;
|
||||
} else {
|
||||
// error grabbing the settings - in some cases this means we are stuck
|
||||
// so we should retry until we get it
|
||||
qDebug() << "Error getting domain settings -" << settingsReply->errorString() << "- retrying";
|
||||
|
||||
if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
|
||||
qDebug() << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS
|
||||
<< "times. Re-setting connection to domain.";
|
||||
clearSettings();
|
||||
clearConnectionInfo();
|
||||
emit settingsReceiveFail();
|
||||
} else {
|
||||
requestDomainSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) {
|
||||
// figure out the port that the DS wants us to use for us to talk to them with DTLS
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#ifndef hifi_DomainHandler_h
|
||||
#define hifi_DomainHandler_h
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUuid>
|
||||
|
@ -33,6 +34,7 @@ public:
|
|||
DomainHandler(QObject* parent = 0);
|
||||
|
||||
void clearConnectionInfo();
|
||||
void clearSettings();
|
||||
|
||||
const QUuid& getUUID() const { return _uuid; }
|
||||
void setUUID(const QUuid& uuid) { _uuid = uuid; }
|
||||
|
@ -54,14 +56,22 @@ public:
|
|||
bool isConnected() const { return _isConnected; }
|
||||
void setIsConnected(bool isConnected);
|
||||
|
||||
bool hasSettings() const { return !_settingsObject.isEmpty(); }
|
||||
void requestDomainSettings() const;
|
||||
const QJsonObject& getSettingsObject() const { return _settingsObject; }
|
||||
|
||||
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
|
||||
|
||||
private slots:
|
||||
void completedHostnameLookup(const QHostInfo& hostInfo);
|
||||
void settingsRequestFinished();
|
||||
signals:
|
||||
void hostnameChanged(const QString& hostname);
|
||||
void connectedToDomain(const QString& hostname);
|
||||
|
||||
void settingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void settingsReceiveFail();
|
||||
|
||||
private:
|
||||
void reset();
|
||||
|
||||
|
@ -71,6 +81,8 @@ private:
|
|||
QUuid _assignmentUUID;
|
||||
bool _isConnected;
|
||||
QTimer* _handshakeTimer;
|
||||
QJsonObject _settingsObject;
|
||||
int _failedSettingsRequests;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainHandler_h
|
||||
|
|
|
@ -70,6 +70,7 @@ enum PacketType {
|
|||
PacketTypeVoxelEditNack,
|
||||
PacketTypeParticleEditNack,
|
||||
PacketTypeModelEditNack,
|
||||
PacketTypeSignedTransactionPayment
|
||||
};
|
||||
|
||||
typedef char PacketVersion;
|
||||
|
|
|
@ -70,7 +70,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
|||
}
|
||||
|
||||
void UserActivityLogger::requestFinished(const QJsonObject& object) {
|
||||
qDebug() << object;
|
||||
// qDebug() << object;
|
||||
}
|
||||
|
||||
void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) {
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
30
libraries/octree/src/EditPacketBuffer.cpp
Normal file
30
libraries/octree/src/EditPacketBuffer.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// EditPacketBuffer.cpp
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-07-30.
|
||||
// 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 "EditPacketBuffer.h"
|
||||
|
||||
EditPacketBuffer::EditPacketBuffer() :
|
||||
_nodeUUID(),
|
||||
_currentType(PacketTypeUnknown),
|
||||
_currentSize(0),
|
||||
_satoshiCost(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
EditPacketBuffer::EditPacketBuffer(PacketType type, unsigned char* buffer, ssize_t length, qint64 satoshiCost, QUuid nodeUUID) :
|
||||
_nodeUUID(nodeUUID),
|
||||
_currentType(type),
|
||||
_currentSize(length),
|
||||
_satoshiCost(satoshiCost)
|
||||
{
|
||||
memcpy(_currentBuffer, buffer, length);
|
||||
};
|
34
libraries/octree/src/EditPacketBuffer.h
Normal file
34
libraries/octree/src/EditPacketBuffer.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// EditPacketBuffer.h
|
||||
// libraries/octree/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-07-30.
|
||||
// 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_EditPacketBuffer_h
|
||||
#define hifi_EditPacketBuffer_h
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <LimitedNodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
/// Used for construction of edit packets
|
||||
class EditPacketBuffer {
|
||||
public:
|
||||
EditPacketBuffer();
|
||||
EditPacketBuffer(PacketType type, unsigned char* codeColorBuffer, ssize_t length,
|
||||
qint64 satoshiCost = 0, const QUuid nodeUUID = QUuid());
|
||||
|
||||
QUuid _nodeUUID;
|
||||
PacketType _currentType;
|
||||
unsigned char _currentBuffer[MAX_PACKET_SIZE];
|
||||
ssize_t _currentSize;
|
||||
qint64 _satoshiCost;
|
||||
};
|
||||
|
||||
#endif // hifi_EditPacketBuffer_h
|
|
@ -17,14 +17,6 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include "OctreeEditPacketSender.h"
|
||||
|
||||
EditPacketBuffer::EditPacketBuffer(PacketType type, unsigned char* buffer, ssize_t length, QUuid nodeUUID) :
|
||||
_nodeUUID(nodeUUID),
|
||||
_currentType(type),
|
||||
_currentSize(length)
|
||||
{
|
||||
memcpy(_currentBuffer, buffer, length);
|
||||
};
|
||||
|
||||
const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND;
|
||||
|
||||
|
||||
|
@ -34,7 +26,10 @@ OctreeEditPacketSender::OctreeEditPacketSender() :
|
|||
_maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES),
|
||||
_releaseQueuedMessagesPending(false),
|
||||
_serverJurisdictions(NULL),
|
||||
_maxPacketSize(MAX_PACKET_SIZE) {
|
||||
_maxPacketSize(MAX_PACKET_SIZE),
|
||||
_destinationWalletUUID()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OctreeEditPacketSender::~OctreeEditPacketSender() {
|
||||
|
@ -87,7 +82,8 @@ bool OctreeEditPacketSender::serversExist() const {
|
|||
|
||||
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
|
||||
// a known nodeID.
|
||||
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) {
|
||||
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer,
|
||||
ssize_t length, qint64 satoshiCost) {
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
|
@ -101,10 +97,16 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
|
|||
unsigned char* sequenceAt = buffer + numBytesPacketHeader;
|
||||
quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++;
|
||||
memcpy(sequenceAt, &sequence, sizeof(quint16));
|
||||
|
||||
|
||||
// send packet
|
||||
QByteArray packet(reinterpret_cast<const char*>(buffer), length);
|
||||
queuePacketForSending(node, packet);
|
||||
|
||||
if (hasDestinationWalletUUID() && satoshiCost > 0) {
|
||||
// if we have a destination wallet UUID and a cost associated with this packet, signal that it
|
||||
// needs to be sent
|
||||
emit octreePaymentRequired(satoshiCost, nodeUUID, _destinationWalletUUID);
|
||||
}
|
||||
|
||||
// add packet to history
|
||||
_sentPacketHistories[nodeUUID].packetSent(sequence, packet);
|
||||
|
@ -120,6 +122,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
|
|||
|
||||
qDebug() << "OctreeEditPacketSender::queuePacketToNode() queued " << buffer[0] <<
|
||||
" - command to node bytes=" << length <<
|
||||
" satoshiCost=" << satoshiCost <<
|
||||
" sequence=" << sequence <<
|
||||
" transitTimeSoFar=" << transitTime << " usecs";
|
||||
}
|
||||
|
@ -135,7 +138,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
|
|||
_pendingPacketsLock.lock();
|
||||
while (!_preServerSingleMessagePackets.empty()) {
|
||||
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
|
||||
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize);
|
||||
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize, packet->_satoshiCost);
|
||||
delete packet;
|
||||
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
|
||||
}
|
||||
|
@ -157,11 +160,12 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length) {
|
||||
void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned char* buffer,
|
||||
ssize_t length, qint64 satoshiCost) {
|
||||
// If we're asked to save messages while waiting for voxel servers to arrive, then do so...
|
||||
|
||||
if (_maxPendingMessages > 0) {
|
||||
EditPacketBuffer* packet = new EditPacketBuffer(type, buffer, length);
|
||||
EditPacketBuffer* packet = new EditPacketBuffer(type, buffer, length, satoshiCost);
|
||||
_pendingPacketsLock.lock();
|
||||
_preServerSingleMessagePackets.push_back(packet);
|
||||
// if we've saved MORE than our max, then clear out the oldest packet...
|
||||
|
@ -175,7 +179,7 @@ void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length) {
|
||||
void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length, qint64 satoshiCost) {
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
}
|
||||
|
@ -202,7 +206,7 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
|
|||
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
|
||||
_serverJurisdictions->unlock();
|
||||
if (isMyJurisdiction) {
|
||||
queuePacketToNode(nodeUUID, buffer, length);
|
||||
queuePacketToNode(nodeUUID, buffer, length, satoshiCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +214,8 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
|
|||
|
||||
|
||||
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
|
||||
void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned char* codeColorBuffer, ssize_t length) {
|
||||
void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned char* codeColorBuffer,
|
||||
ssize_t length, qint64 satoshiCost) {
|
||||
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
|
@ -285,6 +290,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch
|
|||
|
||||
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
|
||||
packetBuffer._currentSize += length;
|
||||
packetBuffer._satoshiCost += satoshiCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +312,8 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
|
|||
void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
|
||||
_releaseQueuedPacketMutex.lock();
|
||||
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PacketTypeUnknown) {
|
||||
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
|
||||
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0],
|
||||
packetBuffer._currentSize, packetBuffer._satoshiCost);
|
||||
packetBuffer._currentSize = 0;
|
||||
packetBuffer._currentType = PacketTypeUnknown;
|
||||
}
|
||||
|
@ -326,6 +333,9 @@ void OctreeEditPacketSender::initializePacket(EditPacketBuffer& packetBuffer, Pa
|
|||
packetBuffer._currentSize += sizeof(quint64); // nudge past timestamp
|
||||
|
||||
packetBuffer._currentType = type;
|
||||
|
||||
// reset cost for packet to 0
|
||||
packetBuffer._satoshiCost = 0;
|
||||
}
|
||||
|
||||
bool OctreeEditPacketSender::process() {
|
||||
|
|
|
@ -15,20 +15,11 @@
|
|||
#include <qqueue.h>
|
||||
#include <PacketSender.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "EditPacketBuffer.h"
|
||||
#include "JurisdictionMap.h"
|
||||
#include "SentPacketHistory.h"
|
||||
|
||||
/// Used for construction of edit packets
|
||||
class EditPacketBuffer {
|
||||
public:
|
||||
EditPacketBuffer() : _nodeUUID(), _currentType(PacketTypeUnknown), _currentSize(0) { }
|
||||
EditPacketBuffer(PacketType type, unsigned char* codeColorBuffer, ssize_t length, const QUuid nodeUUID = QUuid());
|
||||
QUuid _nodeUUID;
|
||||
PacketType _currentType;
|
||||
unsigned char _currentBuffer[MAX_PACKET_SIZE];
|
||||
ssize_t _currentSize;
|
||||
};
|
||||
|
||||
/// Utility for processing, packing, queueing and sending of outbound edit messages.
|
||||
class OctreeEditPacketSender : public PacketSender {
|
||||
Q_OBJECT
|
||||
|
@ -39,7 +30,7 @@ public:
|
|||
/// Queues a single edit message. Will potentially send a pending multi-command packet. Determines which server
|
||||
/// node or nodes the packet should be sent to. Can be called even before servers are known, in which case up to
|
||||
/// MaxPendingMessages will be buffered and processed when servers are known.
|
||||
void queueOctreeEditMessage(PacketType type, unsigned char* buffer, ssize_t length);
|
||||
void queueOctreeEditMessage(PacketType type, unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
|
||||
|
||||
/// Releases all queued messages even if those messages haven't filled an MTU packet. This will move the packed message
|
||||
/// packets onto the send queue. If running in threaded mode, the caller does not need to do any further processing to
|
||||
|
@ -91,18 +82,24 @@ public:
|
|||
// you must override these...
|
||||
virtual char getMyNodeType() const = 0;
|
||||
virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { };
|
||||
|
||||
bool hasDestinationWalletUUID() const { return !_destinationWalletUUID.isNull(); }
|
||||
void setDestinationWalletUUID(const QUuid& destinationWalletUUID) { _destinationWalletUUID = destinationWalletUUID; }
|
||||
const QUuid& getDestinationWalletUUID() { return _destinationWalletUUID; }
|
||||
|
||||
void processNackPacket(const QByteArray& packet);
|
||||
|
||||
public slots:
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
public:
|
||||
void processNackPacket(const QByteArray& packet);
|
||||
|
||||
signals:
|
||||
void octreePaymentRequired(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID);
|
||||
|
||||
protected:
|
||||
bool _shouldSend;
|
||||
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length);
|
||||
void queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length);
|
||||
void queuePacketToNodes(unsigned char* buffer, ssize_t length);
|
||||
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
|
||||
void queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
|
||||
void queuePacketToNodes(unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
|
||||
void initializePacket(EditPacketBuffer& packetBuffer, PacketType type);
|
||||
void releaseQueuedPacket(EditPacketBuffer& packetBuffer); // releases specific queued packet
|
||||
|
||||
|
@ -127,5 +124,7 @@ protected:
|
|||
// TODO: add locks for this and _pendingEditPackets
|
||||
QHash<QUuid, SentPacketHistory> _sentPacketHistories;
|
||||
QHash<QUuid, quint16> _outgoingSequenceNumbers;
|
||||
|
||||
QUuid _destinationWalletUUID;
|
||||
};
|
||||
#endif // hifi_OctreeEditPacketSender_h
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -66,6 +66,20 @@ glm::quat Quat::mix(const glm::quat& q1, const glm::quat& q2, float alpha) {
|
|||
return safeMix(q1, q2, alpha);
|
||||
}
|
||||
|
||||
/// Spherical Linear Interpolation
|
||||
glm::quat Quat::slerp(const glm::quat& q1, const glm::quat& q2, float alpha) {
|
||||
return glm::slerp(q1, q2, alpha);
|
||||
}
|
||||
|
||||
// Spherical Quadratic Interpolation
|
||||
glm::quat Quat::squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h) {
|
||||
return glm::squad(q1, q2, s1, s2, h);
|
||||
}
|
||||
|
||||
float Quat::dot(const glm::quat& q1, const glm::quat& q2) {
|
||||
return glm::dot(q1, q2);
|
||||
}
|
||||
|
||||
void Quat::print(const QString& lable, const glm::quat& q) {
|
||||
qDebug() << qPrintable(lable) << q.x << "," << q.y << "," << q.z << "," << q.w;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ public slots:
|
|||
glm::vec3 safeEulerAngles(const glm::quat& orientation); // degrees
|
||||
glm::quat angleAxis(float angle, const glm::vec3& v); // degrees
|
||||
glm::quat mix(const glm::quat& q1, const glm::quat& q2, float alpha);
|
||||
glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha);
|
||||
glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h);
|
||||
float dot(const glm::quat& q1, const glm::quat& q2);
|
||||
void print(const QString& lable, const glm::quat& q);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
|
|
|
@ -117,9 +117,9 @@ void VoxelEditPacketSender::sendVoxelEditMessage(PacketType type, const VoxelDet
|
|||
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
|
||||
// jurisdictions for processing
|
||||
if (!voxelServersExist()) {
|
||||
queuePendingPacketToNodes(type, bufferOut, sizeOut);
|
||||
queuePendingPacketToNodes(type, bufferOut, sizeOut, satoshiCostForMessage(detail));
|
||||
} else {
|
||||
queuePacketToNodes(bufferOut, sizeOut);
|
||||
queuePacketToNodes(bufferOut, sizeOut, satoshiCostForMessage(detail));
|
||||
}
|
||||
|
||||
// either way, clean up the created buffer
|
||||
|
@ -138,7 +138,20 @@ void VoxelEditPacketSender::queueVoxelEditMessages(PacketType type, int numberOf
|
|||
int sizeOut = 0;
|
||||
|
||||
if (encodeVoxelEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
|
||||
queueOctreeEditMessage(type, bufferOut, sizeOut);
|
||||
queueOctreeEditMessage(type, bufferOut, sizeOut, satoshiCostForMessage(details[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qint64 VoxelEditPacketSender::satoshiCostForMessage(const VoxelDetail& details) {
|
||||
const DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler();
|
||||
|
||||
if (_satoshisPerVoxel == 0 && _satoshisPerMeterCubed == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
float meterScale = details.s * TREE_SCALE;
|
||||
float totalVolume = meterScale * meterScale * meterScale;
|
||||
|
||||
return _satoshisPerVoxel + (qint64) floorf(totalVolume * _satoshisPerMeterCubed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#define hifi_VoxelEditPacketSender_h
|
||||
|
||||
#include <OctreeEditPacketSender.h>
|
||||
|
||||
#include "VoxelDetail.h"
|
||||
|
||||
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
|
||||
|
@ -49,5 +50,14 @@ public:
|
|||
|
||||
// My server type is the voxel server
|
||||
virtual char getMyNodeType() const { return NodeType::VoxelServer; }
|
||||
|
||||
void setSatoshisPerVoxel(qint64 satoshisPerVoxel) { _satoshisPerVoxel = satoshisPerVoxel; }
|
||||
void setSatoshisPerMeterCubed(qint64 satoshisPerMeterCubed) { _satoshisPerMeterCubed = satoshisPerMeterCubed; }
|
||||
|
||||
qint64 satoshiCostForMessage(const VoxelDetail& details);
|
||||
|
||||
private:
|
||||
qint64 _satoshisPerVoxel;
|
||||
qint64 _satoshisPerMeterCubed;
|
||||
};
|
||||
#endif // hifi_VoxelEditPacketSender_h
|
||||
|
|
|
@ -71,7 +71,6 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
|
|||
VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE,
|
||||
scale / (float)TREE_SCALE, red, green, blue};
|
||||
|
||||
|
||||
// handle the local tree also...
|
||||
if (_tree) {
|
||||
if (_undoStack) {
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# add the test directories
|
||||
file(GLOB TEST_SUBDIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*")
|
||||
foreach(DIR ${TEST_SUBDIRS})
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}")
|
||||
add_subdirectory(${DIR})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
endforeach()
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME audio-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME metavoxel-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME networking-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME octree-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME physics-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME shared-tests)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# add the tool directories
|
||||
add_subdirectory(bitstream2json)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME bitstream2json)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME json2bitstream)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME mtc)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
|
@ -1,9 +1,3 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME voxel-edit)
|
||||
|
||||
set(ROOT_DIR ..)
|
||||
|
|
Loading…
Reference in a new issue