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

Conflicts:
	interface/CMakeLists.txt
This commit is contained in:
Atlante45 2014-10-16 15:21:34 -07:00
commit 5d146a00e7
77 changed files with 1971 additions and 3560 deletions

1891
Doxyfile

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <mmintrin.h>
#include <errno.h>
#include <fcntl.h>
#include <fstream>
@ -58,8 +57,6 @@
#include "AvatarAudioStream.h"
#include "InjectedAudioStream.h"
#include "AudioMixer.h"
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
@ -197,20 +194,12 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* l
attenuationCoefficient *= offAxisCoefficient;
}
bool wantBreak = false;
float attenuationPerDoublingInDistance = _attenuationPerDoublingInDistance;
foreach (const QString& source, _attenuationCoefficients.keys()) {
if (_audioZones[source].contains(streamToAdd->getPosition())) {
foreach (const QString& listener, _attenuationCoefficients[source].keys()) {
if (_audioZones[listener].contains(listeningNodeStream->getPosition())) {
attenuationPerDoublingInDistance = _attenuationCoefficients[source][listener];
wantBreak = true;
break;
}
}
}
if (wantBreak) {
for (int i = 0; i < _zonesSettings.length(); ++i) {
if (_audioZones[_zonesSettings[i].source].contains(streamToAdd->getPosition()) &&
_audioZones[_zonesSettings[i].listener].contains(listeningNodeStream->getPosition())) {
attenuationPerDoublingInDistance = _zonesSettings[i].coefficient;
break;
}
}
@ -1028,21 +1017,18 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
coefficientObject.contains(LISTENER) &&
coefficientObject.contains(COEFFICIENT)) {
bool ok;
QString source = coefficientObject.value(SOURCE).toString();
QString listener = coefficientObject.value(LISTENER).toString();
float coefficient = coefficientObject.value(COEFFICIENT).toString().toFloat(&ok);
ZonesSettings settings;
if (ok && coefficient >= 0.0f && coefficient <= 1.0f &&
_audioZones.contains(source) && _audioZones.contains(listener)) {
bool ok;
settings.source = coefficientObject.value(SOURCE).toString();
settings.listener = coefficientObject.value(LISTENER).toString();
settings.coefficient = coefficientObject.value(COEFFICIENT).toString().toFloat(&ok);
if (ok && settings.coefficient >= 0.0f && settings.coefficient <= 1.0f &&
_audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) {
if (!_attenuationCoefficients.contains(source)) {
_attenuationCoefficients.insert(source, QHash<QString, float>());
}
if (!_attenuationCoefficients[source].contains(listener)) {
_attenuationCoefficients[source].insert(listener, coefficient);
qDebug() << "Added Coefficient:" << source << listener << coefficient;
}
_zonesSettings.push_back(settings);
qDebug() << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient;
}
}
}

View file

@ -74,8 +74,14 @@ private:
int _numStatFrames;
int _sumListeners;
int _sumMixes;
QHash<QString, AABox> _audioZones;
QHash<QString, QHash<QString, float> > _attenuationCoefficients;
struct ZonesSettings {
QString source;
QString listener;
float coefficient;
};
QVector<ZonesSettings> _zonesSettings;
static InboundAudioStream::Settings _streamSettings;

View file

@ -861,8 +861,6 @@ void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const H
}
} 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 {
@ -1245,51 +1243,6 @@ 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

View file

@ -127,8 +127,6 @@ public slots:
void nodeKilled(SharedNodePointer node);
void sendStatsPacket();
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
@ -141,7 +139,6 @@ protected:
QString getConfiguration();
QString getStatusLink();
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
void setupDatagramProcessingThread();
int _argc;

View file

@ -38,4 +38,18 @@ endif ()
# link the shared hifi libraries
link_hifi_libraries(embedded-webserver networking shared)
# find OpenSSL
find_package(OpenSSL REQUIRED)
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
"\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.")
endif ()
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
# append OpenSSL to our list of libraries to link
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${OPENSSL_LIBRARIES}")
link_shared_dependencies()

View file

@ -59,6 +59,20 @@
"type": "password",
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
"value-hidden": true
},
{
"name": "allowed_users",
"type": "table",
"label": "Allowed Users",
"help": "List the High Fidelity names for people you want to be able to connect to this domain.<br/>An empty list means everyone.<br/>You can always connect from this machine.",
"numbered": false,
"columns": [
{
"name": "username",
"label": "Username",
"can_set": true
}
]
}
]
},
@ -118,7 +132,8 @@
"type": "table",
"label": "Attenuation Coefficients",
"help": "In this table you can set custom attenuation coefficients between audio zones",
"numbered": false,
"numbered": true,
"can_order": true,
"columns": [
{
"name": "source",
@ -144,7 +159,7 @@
},
{
"name": "audio_buffer",
"label": "Audio Buffer",
"label": "Audio Buffers",
"assignment-types": [0],
"settings": [
{

View file

@ -79,12 +79,19 @@ td.buttons {
width: 14px;
}
td.buttons .glyphicon {
display: block;
td .glyphicon {
text-align: center;
font-size: 12px;
}
td.add-del-buttons .glyphicon {
display: block;
}
td.reorder-buttons .glyphicon {
display: inherit;
}
tr.new-row {
color: #3c763d;
background-color: #dff0d8;

View file

@ -8,7 +8,15 @@ var Settings = {
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
DEL_ROW_BUTTON_CLASS: 'del-row',
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
MOVE_UP_BUTTON_CLASS: 'move-up',
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
MOVE_DOWN_BUTTON_CLASS: 'move-down',
MOVE_DOWN_SPAN_CLASSES: 'glyphicon glyphicon-chevron-down move-down',
TABLE_BUTTONS_CLASS: 'buttons',
ADD_DEL_BUTTONS_CLASS: 'add-del-buttons',
ADD_DEL_BUTTONS_CLASSES: 'buttons add-del-buttons',
REORDER_BUTTONS_CLASS: 'reorder-buttons',
REORDER_BUTTONS_CLASSES: 'buttons reorder-buttons',
NEW_ROW_CLASS: 'new-row'
};
@ -110,6 +118,14 @@ $(document).ready(function(){
$('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
deleteTableRow(this);
})
$('#settings-form').on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
moveTableRow(this, true);
})
$('#settings-form').on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
moveTableRow(this, false);
})
$('#settings-form').on('keypress', 'table input', function(e){
if (e.keyCode == 13) {
@ -120,7 +136,7 @@ $(document).ready(function(){
if (sibling.hasClass(Settings.DATA_COL_CLASS)) {
// set focus to next input
sibling.find('input').focus()
} else if (sibling.hasClass(Settings.TABLE_BUTTONS_CLASS)) {
} else if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
// set focus to the first input in the new row
@ -239,6 +255,10 @@ $('body').on('click', '.save-button', function(e){
function makeTable(setting, setting_name, setting_value) {
var isArray = !_.has(setting, 'key')
if (!isArray && setting.can_order) {
setting.can_order = false;
}
var html = (setting.label) ? "<label class='control-label'>" + setting.label + "</label>" : ""
html += "<span class='help-block'>" + setting.help + "</span>"
html += "<table class='table table-bordered' data-short-name='" + setting.name + "' name='" + setting_name
@ -259,7 +279,11 @@ function makeTable(setting, setting_name, setting_value) {
html += "<td class='data'><strong>" + col.label + "</strong></td>" // Data
})
html += "<td class='buttons'><strong>+/-</strong></td></tr>"
if (setting.can_order) {
html += "<td class=" + Settings.REORDER_BUTTONS_CLASSES +
"><span class='glyphicon glyphicon-sort'></span></td>";
}
html += "<td class=" + Settings.ADD_DEL_BUTTONS_CLASSES + "></td></tr>"
// populate rows in the table from existing values
var row_num = 1
@ -279,13 +303,13 @@ function makeTable(setting, setting_name, setting_value) {
html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
if (isArray) {
colIsArray = _.isArray(row)
colValue = colIsArray ? row : row[col.name]
rowIsObject = setting.columns.length > 1
colValue = rowIsObject ? row[col.name] : row
html += colValue
// for arrays we add a hidden input to this td so that values can be posted appropriately
html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
+ (colIsArray ? "" : "." + col.name) + "' value='" + colValue + "'/>"
+ (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
} else if (row.hasOwnProperty(col.name)) {
html += row[col.name]
}
@ -293,7 +317,13 @@ function makeTable(setting, setting_name, setting_value) {
html += "</td>"
})
html += "<td class='buttons'><span class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></span></td>"
if (setting.can_order) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
"'><span class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></span><span class='" +
Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>"
}
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
"'><span class='" + Settings.DEL_ROW_SPAN_CLASSES + "'></span></td>"
html += "</tr>"
row_num++
@ -324,8 +354,12 @@ function makeTableInputs(setting) {
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' value=''>\
</td>"
})
html += "<td class='buttons'><span class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></span></td>"
if (setting.can_order) {
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
}
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
"'><span class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></span></td>"
html += "</tr>"
return html
@ -418,7 +452,10 @@ function addTableRow(add_glyphicon) {
} else {
$(element).html(1)
}
} else if ($(element).hasClass("buttons")) {
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
$(element).html("<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'><span class='" + Settings.MOVE_UP_SPAN_CLASSES +
"'></span><span class='" + Settings.MOVE_DOWN_SPAN_CLASSES + "'></span></td>")
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
// Change buttons
var span = $(element).children("span")
span.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
@ -499,7 +536,32 @@ function deleteTableRow(delete_glyphicon) {
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table))
}
}
function moveTableRow(move_glyphicon, move_up) {
var row = $(move_glyphicon).closest('tr')
var table = $(row).closest('table')
var isArray = table.data('setting-type') === 'array'
if (!isArray) {
return;
}
if (move_up) {
var prev_row = row.prev()
if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) {
prev_row.before(row)
}
} else {
var next_row = row.next()
if (next_row.hasClass(Settings.DATA_ROW_CLASS)) {
next_row.after(row)
}
}
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table))
}
function updateDataChangedForSiblingRows(row, forceTrue) {
// anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true

View file

@ -22,7 +22,7 @@
<ul class="nav nav-pills nav-stacked">
</ul>
<button id="advanced-toggle-button" class="btn btn-info">Show advanced</button>
<button id="advanced-toggle-button" hidden=true class="btn btn-info">Show advanced</button>
<button class="btn btn-success save-button">Save and restart</button>
</div>
</div>
@ -32,25 +32,32 @@
<script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<div class="panel panel-default" id="<%- group.name %>">
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
<% isAdvanced = _.isEmpty(split_settings[0]) %>
<% if (isAdvanced) { %>
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
<% } %>
<div class="panel panel-default <%- (isAdvanced) ? 'advanced-setting' : '' %>" id="<%- group.name %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
<% _.each(split_settings[0], function(setting) { %>
<%= getFormGroup(group.name, setting, values, false,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% _.each(split_settings[1], function(setting) { %>
<%= getFormGroup(group.name, setting, values, true,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %>
<%= getFormGroup(group.name, setting, values, true,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% }%>
</div>
</div>
<% }); %>
</script>
<div id="panels"></div>
</form>

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <QtCore/QDir>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
@ -44,8 +47,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_oauthProviderURL(),
_oauthClientID(),
_hostname(),
_networkReplyUUIDMap(),
_sessionAuthenticationHash(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_settingsManager()
@ -80,6 +81,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// setup automatic networking settings with data server
setupAutomaticNetworking();
// preload some user public keys so they can connect on first request
preloadAllowedUserPublicKeys();
}
}
@ -507,8 +511,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
}
}
const QString ALLOWED_ROLES_CONFIG_KEY = "allowed-roles";
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
<< NodeType::MetavoxelServer;
@ -517,8 +519,11 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
NodeType_t nodeType;
HifiSockAddr publicSockAddr, localSockAddr;
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
parseNodeDataFromByteArray(packetStream, nodeType, publicSockAddr, localSockAddr, senderSockAddr);
QUuid packetUUID = uuidFromPacketHeader(packet);
@ -551,33 +556,20 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
QString connectedUsername;
if (!isAssignment && !_oauthProviderURL.isEmpty() && _settingsManager.getSettingsMap().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)) {
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;
} else {
// we're letting this user in, don't return and remove their UUID from the hash
_sessionAuthenticationHash.remove(packetUUID);
}
} else {
// we don't know anything about this client
// we have an OAuth provider, ask this interface client to auth against it
QByteArray oauthRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainOAuthRequest);
QDataStream oauthRequestStream(&oauthRequestByteArray, QIODevice::Append);
QUrl authorizationURL = packetUUID.isNull() ? oauthAuthorizationURL() : oauthAuthorizationURL(packetUUID);
oauthRequestStream << authorizationURL;
// send this oauth request datagram back to the client
LimitedNodeList::getInstance()->writeUnverifiedDatagram(oauthRequestByteArray, senderSockAddr);
return;
}
QList<NodeType_t> nodeInterestList;
QString username;
QByteArray usernameSignature;
packetStream >> nodeInterestList >> username >> usernameSignature;
if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr)) {
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied);
// send this oauth request datagram back to the client
LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr);
return;
}
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
@ -610,15 +602,109 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
// if we have a username from an OAuth connect request, set it on the DomainServerNodeData
nodeData->setUsername(connectedUsername);
nodeData->setUsername(username);
nodeData->setSendingSockAddr(senderSockAddr);
// reply back to the user with a PacketTypeDomainList
sendDomainListToNode(newNode, senderSockAddr, nodeInterestListFromPacket(packet, numPreInterestBytes));
sendDomainListToNode(newNode, senderSockAddr, nodeInterestList.toSet());
}
}
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr) {
static const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
static QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
if (allowedUsers.count() > 0) {
// this is an agent, we need to ask them to provide us with their signed username to see if they are allowed in
// we always let in a user who is sending a packet from our local socket or from the localhost address
if (senderSockAddr.getAddress() != LimitedNodeList::getInstance()->getLocalSockAddr().getAddress()
&& senderSockAddr.getAddress() != QHostAddress::LocalHost) {
if (allowedUsers.contains(username)) {
// it's possible this user can be allowed to connect, but we need to check their username signature
QByteArray publicKeyArray = _userPublicKeys.value(username);
if (!publicKeyArray.isEmpty()) {
// if we do have a public key for the user, check for a signature match
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(publicKeyArray.constData());
// first load up the public key into an RSA struct
RSA* rsaPublicKey = d2i_RSAPublicKey(NULL, &publicKeyData, publicKeyArray.size());
if (rsaPublicKey) {
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
int decryptResult = RSA_public_decrypt(usernameSignature.size(),
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
reinterpret_cast<unsigned char*>(decryptedArray.data()),
rsaPublicKey, RSA_PKCS1_PADDING);
if (decryptResult != -1) {
if (username == decryptedArray) {
qDebug() << "Username signature matches for" << username << "- allowing connection.";
// free up the public key before we return
RSA_free(rsaPublicKey);
return true;
} else {
qDebug() << "Username signature did not match for" << username << "- denying connection.";
}
} else {
qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection.";
}
// free up the public key, we don't need it anymore
RSA_free(rsaPublicKey);
} else {
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
}
}
requestUserPublicKey(username);
}
}
} else {
// since we have no allowed user list, let them all in
return true;
}
return false;
}
void DomainServer::preloadAllowedUserPublicKeys() {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
if (allowedUsers.size() > 0) {
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
// going to create > 100 requests
foreach(const QString& username, allowedUsers) {
requestUserPublicKey(username);
}
}
}
void DomainServer::requestUserPublicKey(const QString& username) {
// even if we have a public key for them right now, request a new one in case it has just changed
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
qDebug() << "Requesting public key for user" << username;
AccountManager::getInstance().unauthenticatedRequest(USER_PUBLIC_KEY_PATH.arg(username),
QNetworkAccessManager::GetOperation, callbackParams);
}
QUrl DomainServer::oauthRedirectURL() {
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
}
@ -653,12 +739,9 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
return authorizationURL;
}
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr, const QByteArray& packet,
const HifiSockAddr& senderSockAddr) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
int DomainServer::parseNodeDataFromByteArray(QDataStream& packetStream, NodeType_t& nodeType,
HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr,
const HifiSockAddr& senderSockAddr) {
packetStream >> nodeType;
packetStream >> publicSockAddr >> localSockAddr;
@ -925,7 +1008,30 @@ void DomainServer::sendPendingTransactionsToServer() {
++i;
}
}
}
void DomainServer::publicKeyJSONCallback(QNetworkReply& requestReply) {
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonObject["status"].toString() == "success") {
// figure out which user this is for
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
QString username = usernameRegex.cap(1);
qDebug() << "Storing a public key for user" << username;
// pull the public key as a QByteArray from this response
const QString JSON_DATA_KEY = "data";
const QString JSON_PUBLIC_KEY_KEY = "public_key";
_userPublicKeys[username] =
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
}
}
}
void DomainServer::transactionJSONCallback(const QJsonObject& data) {
@ -1095,17 +1201,24 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress;
QDataStream packetStream(receivedPacket);
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr);
parseNodeDataFromByteArray(packetStream, throwawayNodeType, nodePublicAddress, nodeLocalAddress,
senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID,
nodePublicAddress, nodeLocalAddress);
// update last receive to now
quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
QList<NodeType_t> nodeInterestList;
packetStream >> nodeInterestList;
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestList.toSet());
}
break;
@ -1545,13 +1658,6 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
// 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);
}
}
@ -1695,22 +1801,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
void DomainServer::handleTokenRequestFinished() {
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "-" << "requesting profile.";
QNetworkReply* profileReply = profileRequestGivenTokenReply(networkReply);
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
_networkReplyUUIDMap.insert(profileReply, matchingSessionUUID);
}
}
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
// pull the access token from the returned JSON and store it with the matching session UUID
@ -1719,54 +1809,12 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
// 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.setPath("/api/v1/user/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);
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
QJsonDocument profileJSON = QJsonDocument::fromJson(networkReply->readAll());
if (profileJSON.object()["status"].toString() == "success") {
// pull the user roles from the response
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
QStringList allowedRolesArray = _settingsManager.getSettingsMap().value(ALLOWED_ROLES_CONFIG_KEY).toStringList();
QString connectableUsername;
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
foreach(const QJsonValue& roleValue, userRolesArray) {
if (allowedRolesArray.contains(roleValue.toString())) {
// the user has a role that lets them in
// set the bool to true and break
connectableUsername = profileUsername;
break;
}
}
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, connectableUsername);
}
}
}
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {

View file

@ -52,6 +52,7 @@ public slots:
/// Called by NodeList to inform us a node has been killed
void nodeKilled(SharedNodePointer node);
void publicKeyJSONCallback(QNetworkReply& requestReply);
void transactionJSONCallback(const QJsonObject& data);
void restart();
@ -82,8 +83,17 @@ private:
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr);
void preloadAllowedUserPublicKeys();
void requestUserPublicKey(const QString& username);
int parseNodeDataFromByteArray(QDataStream& packetStream,
NodeType_t& nodeType,
HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr,
const HifiSockAddr& senderSockAddr);
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
const NodeSet& nodeInterestList);
@ -131,13 +141,11 @@ private:
QString _oauthClientID;
QString _oauthClientSecret;
QString _hostname;
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
QHash<QUuid, QString> _sessionAuthenticationHash;
QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
HifiSockAddr _localSockAddr;
QHash<QString, QByteArray> _userPublicKeys;
QHash<QUuid, NetworkPeer> _connectingICEPeers;
QHash<QUuid, HifiSockAddr> _connectedICEPeers;

View file

@ -0,0 +1,246 @@
//
// entityCameraTool.js
// examples
//
// Created by Ryan Huffman on 10/14/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MOUSE_SENSITIVITY = 0.9;
var SCROLL_SENSITIVITY = 0.05;
var PAN_ZOOM_SCALE_RATIO = 0.4;
// Scaling applied based on the size of the object being focused
var FOCUS_ZOOM_SCALE = 1.3;
// Minimum zoom level when focusing on an object
var FOCUS_MIN_ZOOM = 0.5;
// Scaling applied based on the current zoom level
var ZOOM_SCALING = 0.02;
var MIN_ZOOM_DISTANCE = 0.01;
var MAX_ZOOM_DISTANCE = 200;
var MODE_INACTIVE = null;
var MODE_ORBIT = 'orbit';
var MODE_PAN = 'pan';
var EASING_MULTIPLIER = 8;
var INITIAL_ZOOM_DISTANCE = 2;
var INITIAL_ZOOM_DISTANCE_FIRST_PERSON = 3;
EntityCameraTool = function() {
var that = {};
that.enabled = false;
that.mode = MODE_INACTIVE;
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
that.targetZoomDistance = INITIAL_ZOOM_DISTANCE;
that.yaw = 0;
that.pitch = 0;
that.targetYaw = 0;
that.targetPitch = 0;
that.focalPoint = { x: 0, y: 0, z: 0 };
that.targetFocalPoint = { x: 0, y: 0, z: 0 };
that.previousCameraMode = null;
that.lastMousePosition = { x: 0, y: 0 };
that.enable = function() {
if (that.enabled) return;
that.enabled = true;
that.mode = MODE_INACTIVE;
// Pick a point INITIAL_ZOOM_DISTANCE in front of the camera to use as a focal point
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
that.targetZoomDistance = that.zoomDistance;
var focalPoint = Vec3.sum(Camera.getPosition(),
Vec3.multiply(that.zoomDistance, Quat.getFront(Camera.getOrientation())));
if (Camera.getMode() == 'first person') {
that.targetZoomDistance = INITIAL_ZOOM_DISTANCE_FIRST_PERSON;
}
// Determine the correct yaw and pitch to keep the camera in the same location
var dPos = Vec3.subtract(focalPoint, Camera.getPosition());
var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z);
that.targetPitch = -Math.atan2(dPos.y, xzDist) * 180 / Math.PI;
that.targetYaw = Math.atan2(dPos.x, dPos.z) * 180 / Math.PI;
that.pitch = that.targetPitch;
that.yaw = that.targetYaw;
that.focalPoint = focalPoint;
that.setFocalPoint(focalPoint);
that.previousCameraMode = Camera.getMode();
Camera.setMode("independent");
that.updateCamera();
}
that.disable = function() {
if (!that.enabled) return;
that.enabled = false;
that.mode = MODE_INACTIVE;
Camera.setMode(that.previousCameraMode);
}
that.focus = function(entityProperties) {
var dim = entityProperties.dimensions;
var size = Math.max(dim.x, Math.max(dim.y, dim.z));
that.targetZoomDistance = Math.max(size * FOCUS_ZOOM_SCALE, FOCUS_MIN_ZOOM);
that.setFocalPoint(entityProperties.position);
that.updateCamera();
}
that.moveFocalPoint = function(dPos) {
that.setFocalPoint(Vec3.sum(that.focalPoint, dPos));
}
that.setFocalPoint = function(pos) {
that.targetFocalPoint = pos
that.updateCamera();
}
that.mouseMoveEvent = function(event) {
if (that.enabled && that.mode != MODE_INACTIVE) {
if (that.mode == MODE_ORBIT) {
var diffX = event.x - that.lastMousePosition.x;
var diffY = event.y - that.lastMousePosition.y;
that.targetYaw -= MOUSE_SENSITIVITY * (diffX / 5.0)
that.targetPitch += MOUSE_SENSITIVITY * (diffY / 10.0)
while (that.targetYaw > 180.0) that.targetYaw -= 360;
while (that.targetYaw < -180.0) that.targetYaw += 360;
if (that.targetPitch > 90) that.targetPitch = 90;
if (that.targetPitch < -90) that.targetPitch = -90;
that.updateCamera();
} else if (that.mode == MODE_PAN) {
var diffX = event.x - that.lastMousePosition.x;
var diffY = event.y - that.lastMousePosition.y;
var up = Quat.getUp(Camera.getOrientation());
var right = Quat.getRight(Camera.getOrientation());
up = Vec3.multiply(up, diffY * 0.01 * PAN_ZOOM_SCALE_RATIO * that.zoomDistance);
right = Vec3.multiply(right, -diffX * 0.01 * PAN_ZOOM_SCALE_RATIO * that.zoomDistance);
var dPosition = Vec3.sum(up, right);
that.moveFocalPoint(dPosition);
}
that.lastMousePosition.x = event.x;
that.lastMousePosition.y = event.y;
return true;
}
return false;
}
that.mousePressEvent = function(event) {
if (!that.enabled) return;
if (event.isRightButton || (event.isLeftButton && event.isControl && !event.isShifted)) {
that.mode = MODE_ORBIT;
that.lastMousePosition.x = event.x;
that.lastMousePosition.y = event.y;
return true;
} else if (event.isMiddleButton || (event.isLeftButton && event.isControl && event.isShifted)) {
that.mode = MODE_PAN;
that.lastMousePosition.x = event.x;
that.lastMousePosition.y = event.y;
return true;
}
return false;
}
that.mouseReleaseEvent = function(event) {
if (!that.enabled) return;
that.mode = MODE_INACTIVE;
}
that.wheelEvent = function(event) {
if (!that.enabled) return;
var dZoom = -event.delta * SCROLL_SENSITIVITY;
// Scale based on current zoom level
dZoom *= that.targetZoomDistance * ZOOM_SCALING;
that.targetZoomDistance = Math.max(Math.min(that.targetZoomDistance + dZoom, MAX_ZOOM_DISTANCE), MIN_ZOOM_DISTANCE);
that.updateCamera();
}
that.updateCamera = function() {
if (!that.enabled) return;
var yRot = Quat.angleAxis(that.yaw, { x: 0, y: 1, z: 0 });
var xRot = Quat.angleAxis(that.pitch, { x: 1, y: 0, z: 0 });
var q = Quat.multiply(yRot, xRot);
var pos = Vec3.multiply(Quat.getFront(q), that.zoomDistance);
Camera.setPosition(Vec3.sum(that.focalPoint, pos));
yRot = Quat.angleAxis(that.yaw - 180, { x: 0, y: 1, z: 0 });
xRot = Quat.angleAxis(-that.pitch, { x: 1, y: 0, z: 0 });
q = Quat.multiply(yRot, xRot);
Camera.setOrientation(q);
}
function normalizeDegrees(degrees) {
while (degrees > 180) degrees -= 360;
while (degrees < -180) degrees += 360;
return degrees;
}
// Ease the position and orbit of the camera
that.update = function(dt) {
var scale = Math.min(dt * EASING_MULTIPLIER, 1.0);
var dYaw = that.targetYaw - that.yaw;
if (dYaw > 180) dYaw -= 360;
if (dYaw < -180) dYaw += 360;
var dPitch = that.targetPitch - that.pitch;
that.yaw += scale * dYaw;
that.pitch += scale * dPitch;
// Normalize between [-180, 180]
that.yaw = normalizeDegrees(that.yaw);
that.pitch = normalizeDegrees(that.pitch);
var dFocal = Vec3.subtract(that.targetFocalPoint, that.focalPoint);
that.focalPoint = Vec3.sum(that.focalPoint, Vec3.multiply(scale, dFocal));
var dZoom = that.targetZoomDistance - that.zoomDistance;
that.zoomDistance += scale * dZoom;
that.updateCamera();
}
Script.update.connect(that.update);
Controller.wheelEvent.connect(that.wheelEvent);
return that;
}

View file

@ -17,6 +17,8 @@ SelectionDisplay = (function () {
var that = {};
var MINIMUM_DIMENSION = 0.001;
var GRABBER_DISTANCE_TO_SIZE_RATIO = 0.015;
var mode = "UNKNOWN";
var overlayNames = new Array();
@ -174,6 +176,34 @@ SelectionDisplay = (function () {
var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge);
var grabberEdgeFL = Overlays.addOverlay("cube", grabberPropertiesEdge);
var cornerEdgeFaceGrabbers = [
grabberLBN,
grabberRBN,
grabberLBF,
grabberRBF,
grabberLTN,
grabberRTN,
grabberLTF,
grabberRTF,
grabberTOP,
grabberBOTTOM,
grabberLEFT,
grabberRIGHT,
grabberNEAR,
grabberFAR,
grabberEdgeTR,
grabberEdgeTL,
grabberEdgeTF,
grabberEdgeTN,
grabberEdgeBR,
grabberEdgeBL,
grabberEdgeBF,
grabberEdgeBN,
grabberEdgeNR,
grabberEdgeNL,
grabberEdgeFR,
grabberEdgeFL,
];
var baseOverlayAngles = { x: 0, y: 0, z: 0 };
var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles);
@ -2423,6 +2453,32 @@ SelectionDisplay = (function () {
return true;
};
that.updateHandleSizes = function() {
if (selectedEntityProperties) {
var diff = Vec3.subtract(selectedEntityProperties.position, Camera.getPosition());
var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO;
for (var i = 0; i < cornerEdgeFaceGrabbers.length; i++) {
Overlays.editOverlay(cornerEdgeFaceGrabbers[i], {
size: grabberSize,
});
}
var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5;
Overlays.editOverlay(yawHandle, {
scale: handleSize,
});
Overlays.editOverlay(pitchHandle, {
scale: handleSize,
});
Overlays.editOverlay(rollHandle, {
scale: handleSize,
});
Overlays.editOverlay(grabberMoveUp, {
scale: handleSize,
});
}
}
Script.update.connect(that.updateHandleSizes);
that.mouseReleaseEvent = function(event) {
var showHandles = false;
// hide our rotation overlays..., and show our handles

View file

@ -31,6 +31,9 @@ Script.include("libraries/ToolTip.js");
Script.include("libraries/entityPropertyDialogBox.js");
var entityPropertyDialogBox = EntityPropertyDialogBox;
Script.include("libraries/entityCameraTool.js");
var entityCameraTool = new EntityCameraTool();
var windowDimensions = Controller.getViewportDimensions();
var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/";
var toolHeight = 50;
@ -163,6 +166,9 @@ var toolBar = (function () {
Overlays.editOverlay(loadFileMenuItem, { visible: active });
}
var RESIZE_INTERVAL = 50;
var RESIZE_TIMEOUT = 20000;
var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL;
function addModel(url) {
var position;
@ -176,6 +182,27 @@ var toolBar = (function () {
modelURL: url
});
print("Model added: " + url);
var checkCount = 0;
function resize() {
var entityProperties = Entities.getEntityProperties(entityId);
var naturalDimensions = entityProperties.naturalDimensions;
checkCount++;
if (naturalDimensions.x == 0 && naturalDimensions.y == 0 && naturalDimensions.z == 0) {
if (checkCount < RESIZE_MAX_CHECKS) {
Script.setTimeout(resize, RESIZE_INTERVAL);
} else {
print("Resize failed: timed out waiting for model (" + url + ") to load");
}
} else {
entityProperties.dimensions = naturalDimensions;
Entities.editEntity(entityId, entityProperties);
}
}
Script.setTimeout(resize, RESIZE_INTERVAL);
} else {
print("Can't add model: Model would be out of bounds.");
}
@ -217,6 +244,9 @@ var toolBar = (function () {
isActive = !isActive;
if (!isActive) {
selectionDisplay.unselectAll();
entityCameraTool.disable();
} else {
entityCameraTool.enable();
}
return true;
}
@ -292,6 +322,7 @@ var toolBar = (function () {
}
return false;
};
@ -339,7 +370,8 @@ function mousePressEvent(event) {
mouseLastPosition = { x: event.x, y: event.y };
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) {
if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)
|| entityCameraTool.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) {
// Event handled; do nothing.
return;
} else {
@ -442,8 +474,8 @@ function mouseMoveEvent(event) {
return;
}
// allow the selectionDisplay to handle the event first, if it doesn't handle it, then do our own thing
if (selectionDisplay.mouseMoveEvent(event)) {
// allow the selectionDisplay and entityCameraTool to handle the event first, if it doesn't handle it, then do our own thing
if (selectionDisplay.mouseMoveEvent(event) || entityCameraTool.mouseMoveEvent(event)) {
return;
}
@ -482,6 +514,7 @@ function mouseReleaseEvent(event) {
if (entitySelected) {
tooltip.show(false);
}
entityCameraTool.mouseReleaseEvent(event);
}
Controller.mousePressEvent.connect(mousePressEvent);
@ -613,6 +646,10 @@ Controller.keyReleaseEvent.connect(function (event) {
}
if (event.text == "BACKSPACE") {
handeMenuEvent("Delete");
} else if (event.text == "f") {
if (entitySelected) {
entityCameraTool.focus(selectedEntityProperties);
}
}
});

View file

@ -1,101 +0,0 @@
#
# Be sure to run `pod spec lint hifi.podspec' to ensure this is a
# valid spec and remove all comments before submitting the spec.
#
# To learn more about the attributes see http://docs.cocoapods.org/specification.html
#
Pod::Spec.new do |s|
s.name = "hifi"
s.version = "0.0.1"
s.summary = "Test platform for various render and interface tests for next-gen VR system."
s.homepage = "https://github.com/worklist/hifi"
# Specify the license type. CocoaPods detects automatically the license file if it is named
# 'LICENCE*.*' or 'LICENSE*.*', however if the name is different, specify it.
# s.license = 'MIT (example)'
# Specify the authors of the library, with email addresses. You can often find
# the email addresses of the authors by using the SCM log. E.g. $ git log
#
s.author = { "Worklist" => "contact@worklist.net" }
# Specify the location from where the source should be retrieved.
#
s.source = { :git => "https://github.com/worklist/hifi.git" }
s.platform = :ios
s.ios.deployment_target = "6.0"
# A list of file patterns which select the source files that should be
# added to the Pods project. If the pattern is a directory then the
# path will automatically have '*.{h,m,mm,c,cpp}' appended.
#
# s.source_files = 'Classes', 'Classes/**/*.{h,m}'
# s.exclude_files = 'Classes/Exclude'
s.subspec "shared" do |sp|
sp.source_files = 'libraries/shared/src', 'libraries/shared/moc_*'
sp.exclude_files = "libraries/shared/src/UrlReader.*"
sp.dependency 'glm'
sp.xcconfig = { 'CLANG_CXX_LIBRARY' => "libc++" }
end
s.subspec "audio" do |sp|
sp.source_files = "libraries/audio/src"
sp.dependency 'glm'
end
s.subspec "avatars" do |sp|
sp.source_files = 'libraries/avatars/src', 'libraries/avatars/moc_*'
sp.dependency 'glm'
end
s.subspec "voxels" do |sp|
sp.source_files = 'libraries/voxels/src', 'libraries/voxels/moc_*'
sp.dependency 'glm'
end
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '${PODS_ROOT}/../../qt5-device/qtbase/include' }
s.libraries = 'libQtCoreCombined', 'libQt5Network', 'libQt5Script'
# A list of file patterns which select the header files that should be
# made available to the application. If the pattern is a directory then the
# path will automatically have '*.h' appended.
#
# If you do not explicitly set the list of public header files,
# all headers of source_files will be made public.
#
# s.public_header_files = 'Classes/**/*.h'
# A list of paths to preserve after installing the Pod.
# CocoaPods cleans by default any file that is not used.
# Please don't include documentation, example, and test files.
#
# s.preserve_paths = "FilesToSave", "MoreFilesToSave"
# Specify a list of frameworks that the application needs to link
# against for this Pod to work.
#
# s.framework = 'SomeFramework'
# s.frameworks = 'SomeFramework', 'AnotherFramework'
# Specify a list of libraries that the application needs to link
# against for this Pod to work.
#
# s.library = 'iconv'
# s.libraries = 'iconv', 'xml2'
# If this Pod uses ARC, specify it like so.
#
s.requires_arc = false
# If you need to specify any other build settings, add them to the
# xcconfig hash.
#
# s.xcconfig = { 'CLANG_CXX_LIBRARY' => "libc++" }
# Finally, specify any Pods that this Pod depends on.
#
# s.dependency 'JSONKit', '~> 1.4'
end

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "Faceplus" "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "GVERB")
set(OPTIONAL_EXTERNALS "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "GVERB")
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
@ -108,7 +108,6 @@ link_hifi_libraries(shared octree voxels fbx metavoxels networking particles ent
# find any optional and required libraries
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
@ -178,10 +177,9 @@ endif (GVERB_FOUND)
# include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
include_directories("${OPENSSL_INCLUDE_DIR}")
target_link_libraries(
${TARGET_NAME} ${ZLIB_LIBRARIES} ${OPENSSL_LIBRARIES}
${TARGET_NAME} ${ZLIB_LIBRARIES}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
)

View file

@ -1,11 +0,0 @@
Instructions for adding the Faceplus driver to Interface
Andrzej Kapolka, April 8, 2014
1. Copy the Faceplus sdk folders (include, win32) into the interface/external/faceplus folder.
This readme.txt should be there as well.
2. Copy the Faceplus DLLs from the win32 folder into your path.
3. Delete your build directory, run cmake and build, and you should be all set.

View file

@ -72,7 +72,6 @@
#include "InterfaceVersion.h"
#include "Menu.h"
#include "ModelUploader.h"
#include "PaymentManager.h"
#include "Util.h"
#include "devices/MIDIManager.h"
#include "devices/OculusManager.h"
@ -90,7 +89,6 @@
#include "scripting/WindowScriptingInterface.h"
#include "ui/InfoView.h"
#include "ui/OAuthWebViewHandler.h"
#include "ui/Snapshot.h"
#include "ui/Stats.h"
#include "ui/TextRenderer.h"
@ -220,10 +218,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
listenPort = atoi(portStr);
}
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
// make sure it is ready before the NodeList might need it
OAuthWebViewHandler::getInstance();
// start the nodeThread so its event loop is running
_nodeThread->start();
@ -256,11 +250,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDialogDisplayedFlag);
// 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;
@ -1557,9 +1546,8 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox
FaceTracker* Application::getActiveFaceTracker() {
return (_dde.isActive() ? static_cast<FaceTracker*>(&_dde) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL))));
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)));
}
struct SendVoxelsOperationArgs {
@ -1847,7 +1835,6 @@ void Application::init() {
// initialize our face trackers after loading the menu settings
_faceshift.init();
_faceplus.init();
_visage.init();
Leapmotion::init();
@ -3391,7 +3378,6 @@ void Application::resetSensors() {
_mouseX = _glWidget->width() / 2;
_mouseY = _glWidget->height() / 2;
_faceplus.reset();
_faceshift.reset();
_visage.reset();
_dde.reset();
@ -3486,7 +3472,7 @@ void Application::updateLocationInServer() {
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation,
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
}
}
@ -3529,20 +3515,18 @@ void Application::domainChanged(const QString& domainHostname) {
// reset the voxels renderer
_voxels.killLocalVoxels();
// reset the auth URL for OAuth web view handler
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
}
void Application::connectedToDomain(const QString& hostname) {
AccountManager& accountManager = AccountManager::getInstance();
const QUuid& domainID = NodeList::getInstance()->getDomainHandler().getUUID();
if (accountManager.isLoggedIn() && !domainID.isNull()) {
// update our data-server with the domain-server we're logged in with
if (accountManager.isLoggedIn()) {
// update our domain-server with the data-server we're logged in with
QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}";
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), domainPutJsonString.toUtf8());
}
}

View file

@ -58,7 +58,6 @@
#include "avatar/Avatar.h"
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "devices/Faceplus.h"
#include "devices/Faceshift.h"
#include "devices/PrioVR.h"
#include "devices/SixenseManager.h"
@ -214,7 +213,6 @@ public:
int getMouseX() const { return _mouseX; }
int getMouseY() const { return _mouseY; }
bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated;; }
Faceplus* getFaceplus() { return &_faceplus; }
Faceshift* getFaceshift() { return &_faceshift; }
Visage* getVisage() { return &_visage; }
DdeFaceTracker* getDDE() { return &_dde; }
@ -406,7 +404,6 @@ private:
// Various helper functions called during update()
void updateLOD();
void updateMouseRay();
void updateFaceplus();
void updateFaceshift();
void updateVisage();
void updateDDE();
@ -507,7 +504,6 @@ private:
AvatarManager _avatarManager;
MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be)
Faceplus _faceplus;
Faceshift _faceshift;
Visage _visage;
DdeFaceTracker _dde;

View file

@ -26,7 +26,6 @@
#include "AudioSourceTone.h"
#include "AudioSourceNoise.h"
#include "AudioGain.h"
#include "AudioPan.h"
#include "AudioFilter.h"
#include "AudioFilterBank.h"

View file

@ -35,7 +35,6 @@ BuckyBalls::BuckyBalls() {
colors[1] = glm::vec3(0.64f, 0.16f, 0.16f);
colors[2] = glm::vec3(0.31f, 0.58f, 0.80f);
qDebug("Creating buckyballs...");
for (int i = 0; i < NUM_BBALLS; i++) {
_bballPosition[i] = CORNER_BBALLS + randVector() * RANGE_BBALLS;
int element = (rand() % NUM_ELEMENTS);

View file

@ -11,11 +11,11 @@
#include <QtCore/QWeakPointer>
#include <AccountManager.h>
#include <PerfStat.h>
#include "Application.h"
#include "Menu.h"
#include "ui/OAuthWebViewHandler.h"
#include "DatagramProcessor.h"
@ -136,16 +136,12 @@ void DatagramProcessor::processDatagrams() {
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
break;
}
case PacketTypeDomainOAuthRequest: {
QDataStream readStream(incomingPacket);
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
QUrl authorizationURL;
readStream >> authorizationURL;
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
Q_ARG(const QUrl&, authorizationURL));
case PacketTypeDomainConnectionDenied: {
// output to the log so the user knows they got a denied connection request
// and check and signal for an access token so that we can make sure they are logged in
qDebug() << "The domain-server denied a connection request.";
qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature.";
AccountManager::getInstance().checkAndSignalForAccessToken();
break;
}
case PacketTypeMuteEnvironment: {

View file

@ -8,7 +8,7 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Creates single flexible vertlet-integrated strands that can be used for hair/fur/grass
// Creates single flexible verlet-integrated strands that can be used for hair/fur/grass
#include "Hair.h"
@ -17,13 +17,13 @@
const float HAIR_DAMPING = 0.99f;
const float CONSTRAINT_RELAXATION = 10.0f;
const float HAIR_ACCELERATION_COUPLING = 0.025f;
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.01f;
const float HAIR_ANGULAR_ACCELERATION_COUPLING = 0.001f;
const float HAIR_ACCELERATION_COUPLING = 0.045f;
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.020f;
const float HAIR_ANGULAR_ACCELERATION_COUPLING = 0.003f;
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
const float HAIR_STIFFNESS = 0.005f;
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
const float HAIR_STIFFNESS = 0.00f;
const glm::vec3 HAIR_COLOR1(0.98f, 0.76f, 0.075f);
const glm::vec3 HAIR_COLOR2(0.912f, 0.184f, 0.101f);
Hair::Hair(int strands,
int links,
@ -38,7 +38,8 @@ Hair::Hair(int strands,
_acceleration(0.0f),
_angularVelocity(0.0f),
_angularAcceleration(0.0f),
_gravity(0.0f)
_gravity(0.0f),
_loudness(0.0f)
{
_hairPosition = new glm::vec3[_strands * _links];
_hairOriginalPosition = new glm::vec3[_strands * _links];
@ -48,12 +49,15 @@ Hair::Hair(int strands,
_hairColors = new glm::vec3[_strands * _links];
_hairIsMoveable = new int[_strands * _links];
_hairConstraints = new int[_strands * _links * HAIR_CONSTRAINTS]; // Hair can link to two others
const float FACE_WIDTH = PI / 4.0f;
glm::vec3 thisVertex;
for (int strand = 0; strand < _strands; strand++) {
float strandAngle = randFloat() * PI;
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
float azimuth;
float elevation = PI_OVER_TWO - (randFloat() * 0.10f * PI);
azimuth = PI_OVER_TWO;
if (randFloat() < 0.5f) {
azimuth *= -1.0f;
}
glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
thisStrand *= _radius;
@ -77,7 +81,7 @@ Hair::Hair(int strands,
_hairOriginalPosition[vertexIndex] = _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex] = thisVertex;
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * _hairThickness, 0.f, sin(strandAngle) * _hairThickness);
_hairQuadDelta[vertexIndex] *= 1.f - ((float)link / _links);
_hairQuadDelta[vertexIndex] *= ((float)link / _links);
_hairNormals[vertexIndex] = glm::normalize(randVector());
if (randFloat() < elevation / PI_OVER_TWO) {
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)_links);
@ -86,7 +90,9 @@ Hair::Hair(int strands,
}
}
}
}
}
const float SOUND_THRESHOLD = 50.0f;
void Hair::simulate(float deltaTime) {
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
@ -114,9 +120,15 @@ void Hair::simulate(float deltaTime) {
_hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
(_radius - glm::length(_hairPosition[vertexIndex]));
}
// Add random thing driven by loudness
float loudnessFactor = (_loudness > SOUND_THRESHOLD) ? logf(_loudness - SOUND_THRESHOLD) / 8000.0f : 0.0f;
const float QUIESCENT_LOUDNESS = 0.0f;
_hairPosition[vertexIndex] += randVector() * (QUIESCENT_LOUDNESS + loudnessFactor) * ((float)link / (float)_links);
// Add gravity
_hairPosition[vertexIndex] += _gravity * deltaTime;
const float SCALE_GRAVITY = 0.10f;
_hairPosition[vertexIndex] += _gravity * deltaTime * SCALE_GRAVITY;
// Add linear acceleration
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
@ -168,7 +180,7 @@ void Hair::simulate(float deltaTime) {
}
}
// Store start position for next vertlet pass
// Store start position for next verlet pass
_hairLastPosition[vertexIndex] = thisPosition;
}
}
@ -179,11 +191,23 @@ void Hair::render() {
//
// Before calling this function, translate/rotate to the origin of the owning object
//
float loudnessFactor = (_loudness > SOUND_THRESHOLD) ? logf(_loudness - SOUND_THRESHOLD) / 16.0f : 0.0f;
const int SPARKLE_EVERY = 5;
const float HAIR_SETBACK = 0.0f;
int sparkleIndex = (int) (randFloat() * SPARKLE_EVERY);
glPushMatrix();
glTranslatef(0.f, 0.f, HAIR_SETBACK);
glBegin(GL_QUADS);
for (int strand = 0; strand < _strands; strand++) {
for (int link = 0; link < _links - 1; link++) {
int vertexIndex = strand * _links + link;
glColor3fv(&_hairColors[vertexIndex].x);
glm::vec3 thisColor = _hairColors[vertexIndex];
if (sparkleIndex % SPARKLE_EVERY == 0) {
thisColor.x += (1.f - thisColor.x) * loudnessFactor;
thisColor.y += (1.f - thisColor.y) * loudnessFactor;
thisColor.z += (1.f - thisColor.z) * loudnessFactor;
}
glColor3fv(&thisColor.x);
glNormal3fv(&_hairNormals[vertexIndex].x);
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
@ -198,9 +222,11 @@ void Hair::render() {
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
sparkleIndex++;
}
}
glEnd();
glPopMatrix();
}

View file

@ -23,11 +23,11 @@
const int HAIR_CONSTRAINTS = 2;
const int DEFAULT_HAIR_STRANDS = 50;
const int DEFAULT_HAIR_STRANDS = 20;
const int DEFAULT_HAIR_LINKS = 10;
const float DEFAULT_HAIR_RADIUS = 0.15f;
const float DEFAULT_HAIR_LINK_LENGTH = 0.03f;
const float DEFAULT_HAIR_THICKNESS = 0.015f;
const float DEFAULT_HAIR_LINK_LENGTH = 0.04f;
const float DEFAULT_HAIR_THICKNESS = 0.025f;
class Hair {
public:
@ -42,6 +42,7 @@ public:
void setAngularVelocity(const glm::vec3& angularVelocity) { _angularVelocity = angularVelocity; }
void setAngularAcceleration(const glm::vec3& angularAcceleration) { _angularAcceleration = angularAcceleration; }
void setGravity(const glm::vec3& gravity) { _gravity = gravity; }
void setLoudness(const float loudness) { _loudness = loudness; }
private:
int _strands;
@ -61,7 +62,7 @@ private:
glm::vec3 _angularVelocity;
glm::vec3 _angularAcceleration;
glm::vec3 _gravity;
float _loudness;
};

View file

@ -122,7 +122,8 @@ Menu::Menu() :
_hasLoginDialogDisplayed(false),
_snapshotsLocation(),
_scriptsLocation(),
_walletPrivateKey()
_walletPrivateKey(),
_shouldRenderTableNeedsRebuilding(true)
{
Application *appInstance = Application::getInstance();
@ -407,10 +408,6 @@ Menu::Menu() :
appInstance->getFaceshift(),
SLOT(setTCPEnabled(bool)));
#endif
#ifdef HAVE_FACEPLUS
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Faceplus, 0, true,
appInstance->getFaceplus(), SLOT(updateEnabled()));
#endif
#ifdef HAVE_VISAGE
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Visage, 0, false,
appInstance->getVisage(), SLOT(updateEnabled()));
@ -426,6 +423,10 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
QMenu* modelCullingMenu = modelDebugMenu->addMenu("Culling");
addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false);
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels");
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
@ -1550,6 +1551,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
&& _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
_voxelSizeScale *= ADJUST_LOD_DOWN_BY;
if (_voxelSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_voxelSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
}
@ -1572,6 +1574,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
}
if (changed) {
_shouldRenderTableNeedsRebuilding = true;
if (_lodToolsDialog) {
_lodToolsDialog->reloadSliders();
}
@ -1586,14 +1589,56 @@ void Menu::resetLODAdjust() {
void Menu::setVoxelSizeScale(float sizeScale) {
_voxelSizeScale = sizeScale;
_shouldRenderTableNeedsRebuilding = true;
bumpSettings();
}
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust;
_shouldRenderTableNeedsRebuilding = true;
bumpSettings();
}
// TODO: This is essentially the same logic used to render voxels, but since models are more detailed then voxels
// I've added a voxelToModelRatio that adjusts how much closer to a model you have to be to see it.
bool Menu::shouldRenderMesh(float largestDimension, float distanceToCamera) {
const float voxelToMeshRatio = 4.0f; // must be this many times closer to a mesh than a voxel to see it.
float voxelSizeScale = getVoxelSizeScale();
int boundaryLevelAdjust = getBoundaryLevelAdjust();
float maxScale = (float)TREE_SCALE;
float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, voxelSizeScale) / voxelToMeshRatio;
if (_shouldRenderTableNeedsRebuilding) {
_shouldRenderTable.clear();
float SMALLEST_SCALE_IN_TABLE = 0.001f; // 1mm is plenty small
float scale = maxScale;
float visibleDistanceAtScale = visibleDistanceAtMaxScale;
while (scale > SMALLEST_SCALE_IN_TABLE) {
scale /= 2.0f;
visibleDistanceAtScale /= 2.0f;
_shouldRenderTable[scale] = visibleDistanceAtScale;
}
_shouldRenderTableNeedsRebuilding = false;
}
float closestScale = maxScale;
float visibleDistanceAtClosestScale = visibleDistanceAtMaxScale;
QMap<float, float>::const_iterator lowerBound = _shouldRenderTable.lowerBound(largestDimension);
if (lowerBound != _shouldRenderTable.constEnd()) {
closestScale = lowerBound.key();
visibleDistanceAtClosestScale = lowerBound.value();
}
if (closestScale < largestDimension) {
visibleDistanceAtClosestScale *= 2.0f;
}
return (distanceToCamera <= visibleDistanceAtClosestScale);
}
void Menu::lodTools() {
if (!_lodToolsDialog) {
_lodToolsDialog = new LodToolsDialog(Application::getInstance()->getGLWidget());

View file

@ -143,6 +143,8 @@ public:
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
bool shouldRenderMesh(float largestDimension, float distanceToCamera);
#ifdef Q_OS_MAC
SpeechRecognizer* getSpeechRecognizer() { return &_speechRecognizer; }
#endif
@ -310,6 +312,9 @@ private:
QString _snapshotsLocation;
QString _scriptsLocation;
QByteArray _walletPrivateKey;
bool _shouldRenderTableNeedsRebuilding;
QMap<float, float> _shouldRenderTable;
};
@ -367,6 +372,9 @@ namespace MenuOption {
const QString Collisions = "Collisions";
const QString Console = "Console...";
const QString ControlWithSpeech = "Control With Speech";
const QString DontCullOutOfViewMeshParts = "Don't Cull Out Of View Mesh Parts";
const QString DontCullTooSmallMeshParts = "Don't Cull Too Small Mesh Parts";
const QString DontReduceMaterialSwitches = "Don't Attempt to Reduce Material Switches";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DisableActivityLogger = "Disable Activity Logger";
@ -391,7 +399,6 @@ namespace MenuOption {
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
const QString ExpandPaintGLTiming = "Expand /paintGL";
const QString ExpandUpdateTiming = "Expand /update";
const QString Faceplus = "Faceplus";
const QString Faceshift = "Faceshift";
const QString FilterSixense = "Smooth Sixense Movement";
const QString FirstPerson = "First Person";

View file

@ -364,7 +364,9 @@ void ModelUploader::send() {
_progressBar = NULL;
}
void ModelUploader::checkJSON(const QJsonObject& jsonResponse) {
void ModelUploader::checkJSON(QNetworkReply& requestReply) {
QJsonObject jsonResponse = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") {
qDebug() << "status : success";
JSONCallbackParameters callbackParams;
@ -426,7 +428,7 @@ void ModelUploader::uploadUpdate(qint64 bytesSent, qint64 bytesTotal) {
}
}
void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) {
void ModelUploader::uploadSuccess(QNetworkReply& requestReply) {
if (_progressDialog) {
_progressDialog->accept();
}

View file

@ -40,9 +40,9 @@ public slots:
void send();
private slots:
void checkJSON(const QJsonObject& jsonResponse);
void checkJSON(QNetworkReply& requestReply);
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
void uploadSuccess(const QJsonObject& jsonResponse);
void uploadSuccess(QNetworkReply& requestReply);
void uploadFailed(QNetworkReply& errorReply);
void checkS3();
void processCheck();

View file

@ -1,64 +0,0 @@
//
// 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 <Menu.h>
#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) {
QByteArray walletPrivateKeyByteArray = Menu::getInstance()->getWalletPrivateKey();
if (!walletPrivateKeyByteArray.isEmpty()) {
// 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());
QByteArray signedMessageDigest = newTransaction.signedMessageDigest();
if (!signedMessageDigest.isEmpty()) {
transactionByteArray.append(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));
} else {
qDebug() << "Payment of" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID <<
"cannot be sent because there was an error signing the transaction.";
}
} else {
qDebug() << "Payment of" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID <<
"cannot be sent because there is no stored wallet private key.";
}
}

View file

@ -1,25 +0,0 @@
//
// 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

View file

@ -1,86 +0,0 @@
//
// SignedWalletTransaction.cpp
// interface/src
//
// Created by Stephen Birarda on 2014-07-11.
// 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/QCryptographicHash>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <AccountManager.h>
#include "Menu.h"
#include "SignedWalletTransaction.h"
SignedWalletTransaction::SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount,
qint64 messageTimestamp, qint64 expiryDelta) :
WalletTransaction(destinationUUID, amount),
_messageTimestamp(messageTimestamp),
_expiryDelta(expiryDelta)
{
}
QByteArray SignedWalletTransaction::binaryMessage() {
// build the message using the components of this transaction
// UUID, source UUID, destination UUID, message timestamp, expiry delta, amount
QByteArray messageBinary;
messageBinary.append(_uuid.toRfc4122());
messageBinary.append(reinterpret_cast<const char*>(&_messageTimestamp), sizeof(_messageTimestamp));
messageBinary.append(reinterpret_cast<const char*>(&_expiryDelta), sizeof(_expiryDelta));
messageBinary.append(AccountManager::getInstance().getAccountInfo().getWalletID().toRfc4122());
messageBinary.append(_destinationUUID.toRfc4122());
messageBinary.append(reinterpret_cast<const char*>(&_amount), sizeof(_amount));
return messageBinary;
}
QByteArray SignedWalletTransaction::hexMessage() {
return binaryMessage().toHex();
}
QByteArray SignedWalletTransaction::messageDigest() {
return QCryptographicHash::hash(hexMessage(), QCryptographicHash::Sha256).toHex();
}
QByteArray SignedWalletTransaction::signedMessageDigest() {
// pull the current private key from menu into RSA structure in memory
QByteArray privateKeyByteArray = Menu::getInstance()->getWalletPrivateKey();
BIO* privateKeyBIO = NULL;
RSA* rsaPrivateKey = NULL;
privateKeyBIO = BIO_new_mem_buf(privateKeyByteArray.data(), privateKeyByteArray.size());
PEM_read_bio_RSAPrivateKey(privateKeyBIO, &rsaPrivateKey, NULL, NULL);
QByteArray digestToEncrypt = messageDigest();
QByteArray encryptedDigest(RSA_size(rsaPrivateKey), 0);
int encryptReturn = RSA_private_encrypt(digestToEncrypt.size(),
reinterpret_cast<const unsigned char*>(digestToEncrypt.constData()),
reinterpret_cast<unsigned char*>(encryptedDigest.data()),
rsaPrivateKey, RSA_PKCS1_PADDING);
// free the two structures used
BIO_free(privateKeyBIO);
RSA_free(rsaPrivateKey);
return (encryptReturn != -1) ? encryptedDigest : QByteArray();
}

View file

@ -1,32 +0,0 @@
//
// SignedWalletTransaction.h
// interfac/src
//
// Created by Stephen Birarda on 2014-07-11.
// 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_SignedWalletTransaction_h
#define hifi_SignedWalletTransaction_h
#include <WalletTransaction.h>
class SignedWalletTransaction : public WalletTransaction {
Q_OBJECT
public:
SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount, qint64 messageTimestamp, qint64 expiryDelta);
QByteArray binaryMessage();
QByteArray hexMessage();
QByteArray messageDigest();
QByteArray signedMessageDigest();
private:
qint64 _messageTimestamp;
qint64 _expiryDelta;
};
#endif // hifi_SignedWalletTransaction_h

View file

@ -37,92 +37,7 @@ using namespace std;
#define WORKAROUND_BROKEN_GLUT_STROKES
// see http://www.opengl.org/resources/libraries/glut/spec3/node78.html
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * front, glm::vec3 * right, glm::vec3 * up) {
//
// Converts from three euler angles to the associated orthonormal vectors
//
// Angles contains (pitch, yaw, roll) in radians
//
// First, create the quaternion associated with these euler angles
glm::quat q(glm::vec3(angles->x, -(angles->y), angles->z));
// Next, create a rotation matrix from that quaternion
glm::mat4 rotation;
rotation = glm::mat4_cast(q);
// Transform the original vectors by the rotation matrix to get the new vectors
glm::vec4 qup(0,1,0,0);
glm::vec4 qright(-1,0,0,0);
glm::vec4 qfront(0,0,1,0);
glm::vec4 upNew = qup*rotation;
glm::vec4 rightNew = qright*rotation;
glm::vec4 frontNew = qfront*rotation;
// Copy the answers to output vectors
up->x = upNew.x; up->y = upNew.y; up->z = upNew.z;
right->x = rightNew.x; right->y = rightNew.y; right->z = rightNew.z;
front->x = frontNew.x; front->y = frontNew.y; front->z = frontNew.z;
}
void printVector(glm::vec3 vec) {
qDebug("%4.2f, %4.2f, %4.2f", vec.x, vec.y, vec.z);
}
// Return the azimuth angle (in radians) between two points.
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos) {
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z);
}
// Return the angle (in radians) between the head and an object in the scene.
// The value is zero if you are looking right at it.
// The angle is negative if the object is to your right.
float angle_to(glm::vec3 head_pos, glm::vec3 source_pos, float render_yaw, float head_yaw) {
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z) + render_yaw + head_yaw;
}
// Draw a 3D vector floating in space
void drawVector(glm::vec3 * vector) {
glDisable(GL_LIGHTING);
glEnable(GL_POINT_SMOOTH);
glPointSize(3.0);
glLineWidth(2.0);
// Draw axes
glBegin(GL_LINES);
glColor3f(1,0,0);
glVertex3f(0,0,0);
glVertex3f(1,0,0);
glColor3f(0,1,0);
glVertex3f(0,0,0);
glVertex3f(0, 1, 0);
glColor3f(0,0,1);
glVertex3f(0,0,0);
glVertex3f(0, 0, 1);
glEnd();
// Draw the vector itself
glBegin(GL_LINES);
glColor3f(1,1,1);
glVertex3f(0,0,0);
glVertex3f(vector->x, vector->y, vector->z);
glEnd();
// Draw spheres for magnitude
glPushMatrix();
glColor3f(1,0,0);
glTranslatef(vector->x, 0, 0);
Application::getInstance()->getGeometryCache()->renderSphere(0.02f, 10, 10);
glColor3f(0,1,0);
glTranslatef(-vector->x, vector->y, 0);
Application::getInstance()->getGeometryCache()->renderSphere(0.02f, 10, 10);
glColor3f(0,0,1);
glTranslatef(0, -vector->y, vector->z);
Application::getInstance()->getGeometryCache()->renderSphere(0.02f, 10, 10);
glPopMatrix();
}
void renderWorldBox() {
// Show edge of world
@ -201,10 +116,6 @@ int widthText(float scale, int mono, char const* string) {
return textRenderer(mono)->computeWidth(string) * (scale / 0.10);
}
float widthChar(float scale, int mono, char ch) {
return textRenderer(mono)->computeWidth(ch) * (scale / 0.10);
}
void drawText(int x, int y, float scale, float radians, int mono,
char const* string, const float* color) {
//
@ -219,29 +130,6 @@ void drawText(int x, int y, float scale, float radians, int mono,
glPopMatrix();
}
void drawvec3(int x, int y, float scale, float radians, float thick, int mono, glm::vec3 vec, float r, float g, float b) {
//
// Draws vec3 on screen as stroked so it can be resized
//
char vectext[20];
sprintf(vectext,"%3.1f,%3.1f,%3.1f", vec.x, vec.y, vec.z);
int len, i;
glPushMatrix();
glTranslatef(static_cast<float>(x), static_cast<float>(y), 0);
glColor3f(r,g,b);
glRotated(180.0 + double(radians * DEGREES_PER_RADIAN), 0.0, 0.0, 1.0);
glRotated(180.0, 0.0, 1.0, 0.0);
glLineWidth(thick);
glScalef(scale, scale, 1.f);
len = (int) strlen(vectext);
for (i = 0; i < len; i++) {
if (!mono) glutStrokeCharacter(GLUT_STROKE_ROMAN, int(vectext[i]));
else glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, int(vectext[i]));
}
glPopMatrix();
}
void renderCollisionOverlay(int width, int height, float magnitude, float red, float blue, float green) {
const float MIN_VISIBLE_COLLISION = 0.01f;
if (magnitude > MIN_VISIBLE_COLLISION) {
@ -255,27 +143,6 @@ void renderCollisionOverlay(int width, int height, float magnitude, float red, f
}
}
void renderSphereOutline(glm::vec3 position, float radius, int numSides, glm::vec3 cameraPosition) {
glm::vec3 vectorToPosition(glm::normalize(position - cameraPosition));
glm::vec3 right = glm::cross(vectorToPosition, glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 up = glm::cross(right, vectorToPosition);
glBegin(GL_LINE_STRIP);
for (int i=0; i<numSides+1; i++) {
float r = ((float)i / (float)numSides) * TWO_PI;
float s = radius * sinf(r);
float c = radius * cosf(r);
glVertex3f
(
position.x + right.x * s + up.x * c,
position.y + right.y * s + up.y * c,
position.z + right.z * s + up.z * c
);
}
glEnd();
}
void renderCircle(glm::vec3 position, float radius, glm::vec3 surfaceNormal, int numSides) {
@ -321,54 +188,6 @@ void renderBevelCornersRect(int x, int y, int width, int height, int bevelDistan
glEnd();
}
void renderRoundedCornersRect(int x, int y, int width, int height, int radius, int numPointsCorner) {
#define MAX_POINTS_CORNER 50
// At least "2" is needed
if (numPointsCorner <= 1) {
return;
}
if (numPointsCorner > MAX_POINTS_CORNER) {
numPointsCorner = MAX_POINTS_CORNER;
}
// Precompute sin and cos for [0, PI/2) for the number of points (numPointCorner)
double radiusTimesSin[MAX_POINTS_CORNER];
double radiusTimesCos[MAX_POINTS_CORNER];
int i = 0;
for (int i = 0; i < numPointsCorner; i++) {
double t = (double)i * (double)PI_OVER_TWO / (double)(numPointsCorner - 1);
radiusTimesSin[i] = radius * sin(t);
radiusTimesCos[i] = radius * cos(t);
}
glm::dvec2 cornerCenter;
glBegin(GL_POINTS);
// Top left corner
cornerCenter = glm::vec2(x + radius, y + height - radius);
for (i = 0; i < numPointsCorner; i++) {
glVertex2d(cornerCenter.x - radiusTimesCos[i], cornerCenter.y + radiusTimesSin[i]);
}
// Top rigth corner
cornerCenter = glm::vec2(x + width - radius, y + height - radius);
for (i = 0; i < numPointsCorner; i++) {
glVertex2d(cornerCenter.x + radiusTimesSin[i], cornerCenter.y + radiusTimesCos[i]);
}
// Bottom right
cornerCenter = glm::vec2(x + width - radius, y + radius);
for (i = 0; i < numPointsCorner; i++) {
glVertex2d(cornerCenter.x + radiusTimesCos[i], cornerCenter.y - radiusTimesSin[i]);
}
// Bottom left
cornerCenter = glm::vec2(x + radius, y + radius);
for (i = 0; i < numPointsCorner; i++) {
glVertex2d(cornerCenter.x - radiusTimesSin[i], cornerCenter.y - radiusTimesCos[i]);
}
glEnd();
}
void renderOrientationDirections(glm::vec3 position, const glm::quat& orientation, float size) {
@ -395,12 +214,6 @@ void renderOrientationDirections(glm::vec3 position, const glm::quat& orientatio
glEnd();
}
bool closeEnoughForGovernmentWork(float a, float b) {
float distance = std::abs(a-b);
//qDebug("closeEnoughForGovernmentWork() a=%1.10f b=%1.10f distance=%1.10f\n",a,b,distance);
return (distance < 0.00001f);
}
// Do some basic timing tests and report the results
void runTimingTests() {
// How long does it take to make a call to get the time?

View file

@ -16,35 +16,20 @@
#include <glm/gtc/quaternion.hpp>
#include <QSettings>
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);
float angle_to(glm::vec3 head_pos, glm::vec3 source_pos, float render_yaw, float head_yaw);
float randFloat();
const glm::vec3 randVector();
void renderWorldBox();
int widthText(float scale, int mono, char const* string);
float widthChar(float scale, int mono, char ch);
void drawText(int x, int y, float scale, float radians, int mono,
char const* string, const float* color);
void drawvec3(int x, int y, float scale, float radians, float thick, int mono, glm::vec3 vec,
float r=1.0, float g=1.0, float b=1.0);
void drawVector(glm::vec3* vector);
void printVector(glm::vec3 vec);
void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0);
void renderOrientationDirections( glm::vec3 position, const glm::quat& orientation, float size );
void renderSphereOutline(glm::vec3 position, float radius, int numSides, glm::vec3 cameraPosition);
void renderCircle(glm::vec3 position, float radius, glm::vec3 surfaceNormal, int numSides );
void renderRoundedCornersRect(int x, int y, int width, int height, int radius, int numPointsCorner);
void renderBevelCornersRect(int x, int y, int width, int height, int bevelDistance);
void runTimingTests();

View file

@ -192,6 +192,7 @@ void Avatar::simulate(float deltaTime) {
_hair.setAngularVelocity((getAngularVelocity() + getHead()->getAngularVelocity()) * getHead()->getFinalOrientationInWorldFrame());
_hair.setAngularAcceleration(getAngularAcceleration() * getHead()->getFinalOrientationInWorldFrame());
_hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame());
_hair.setLoudness((float) getHeadData()->getAudioLoudness());
_hair.simulate(deltaTime);
}
}

View file

@ -218,6 +218,7 @@ void MyAvatar::simulate(float deltaTime) {
_hair.setAngularVelocity((getAngularVelocity() + getHead()->getAngularVelocity()) * getHead()->getFinalOrientationInWorldFrame());
_hair.setAngularAcceleration(getAngularAcceleration() * getHead()->getFinalOrientationInWorldFrame());
_hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame());
_hair.setLoudness((float)getHeadData()->getAudioLoudness());
_hair.simulate(deltaTime);
}
}

View file

@ -1,254 +0,0 @@
//
// Faceplus.cpp
// interface/src/devices
//
// Created by Andrzej Kapolka on 4/9/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QThread>
#ifdef HAVE_FACEPLUS
#include <faceplus.h>
#endif
#include <FBXReader.h>
#include "Application.h"
#include "Faceplus.h"
static int floatVectorMetaTypeId = qRegisterMetaType<QVector<float> >();
Faceplus::Faceplus() :
_enabled(false),
_active(false) {
#ifdef HAVE_FACEPLUS
// these are ignored--any values will do
faceplus_log_in("username", "password");
#endif
}
Faceplus::~Faceplus() {
setEnabled(false);
}
void Faceplus::init() {
connect(Application::getInstance()->getFaceshift(), SIGNAL(connectionStateChanged()), SLOT(updateEnabled()));
updateEnabled();
}
void Faceplus::reset() {
if (_enabled) {
QMetaObject::invokeMethod(_reader, "reset");
}
}
void Faceplus::setState(const glm::vec3& headTranslation, const glm::quat& headRotation,
float estimatedEyePitch, float estimatedEyeYaw, const QVector<float>& blendshapeCoefficients) {
_headTranslation = headTranslation;
_headRotation = headRotation;
_estimatedEyePitch = estimatedEyePitch;
_estimatedEyeYaw = estimatedEyeYaw;
_blendshapeCoefficients = blendshapeCoefficients;
_active = true;
}
void Faceplus::updateEnabled() {
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceplus) &&
!(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) &&
Application::getInstance()->getFaceshift()->isConnectedOrConnecting()));
}
void Faceplus::setEnabled(bool enabled) {
if (_enabled == enabled) {
return;
}
if ((_enabled = enabled)) {
_reader = new FaceplusReader();
QThread* readerThread = new QThread(this);
_reader->moveToThread(readerThread);
readerThread->start();
QMetaObject::invokeMethod(_reader, "init");
} else {
QThread* readerThread = _reader->thread();
QMetaObject::invokeMethod(_reader, "shutdown");
readerThread->wait();
delete readerThread;
_active = false;
}
}
#ifdef HAVE_FACEPLUS
static QMultiHash<QByteArray, QPair<int, float> > createChannelNameMap() {
QMultiHash<QByteArray, QPair<QByteArray, float> > blendshapeMap;
blendshapeMap.insert("EyeBlink_L", QPair<QByteArray, float>("Mix::Blink_Left", 1.0f));
blendshapeMap.insert("EyeBlink_R", QPair<QByteArray, float>("Mix::Blink_Right", 1.0f));
blendshapeMap.insert("BrowsD_L", QPair<QByteArray, float>("Mix::BrowsDown_Left", 1.0f));
blendshapeMap.insert("BrowsD_R", QPair<QByteArray, float>("Mix::BrowsDown_Right", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::BrowsIn_Left", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::BrowsIn_Right", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::BrowsOuterLower_Left", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::BrowsOuterLower_Right", 1.0f));
blendshapeMap.insert("BrowsU_L", QPair<QByteArray, float>("Mix::BrowsUp_Left", 10.0f));
blendshapeMap.insert("BrowsU_R", QPair<QByteArray, float>("Mix::BrowsUp_Right", 10.0f));
blendshapeMap.insert("EyeOpen_L", QPair<QByteArray, float>("Mix::EyesWide_Left", 1.0f));
blendshapeMap.insert("EyeOpen_R", QPair<QByteArray, float>("Mix::EyesWide_Right", 1.0f));
blendshapeMap.insert("MouthFrown_L", QPair<QByteArray, float>("Mix::Frown_Left", 1.0f));
blendshapeMap.insert("MouthFrown_R", QPair<QByteArray, float>("Mix::Frown_Right", 1.0f));
blendshapeMap.insert("JawLeft", QPair<QByteArray, float>("Mix::Jaw_RotateY_Left", 1.0f));
blendshapeMap.insert("JawRight", QPair<QByteArray, float>("Mix::Jaw_RotateY_Right", 1.0f));
blendshapeMap.insert("LipsLowerDown", QPair<QByteArray, float>("Mix::LowerLipDown_Left", 0.5f));
blendshapeMap.insert("LipsLowerDown", QPair<QByteArray, float>("Mix::LowerLipDown_Right", 0.5f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::LowerLipIn", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::LowerLipOut", 1.0f));
blendshapeMap.insert("MouthLeft", QPair<QByteArray, float>("Mix::Midmouth_Left", 1.0f));
blendshapeMap.insert("MouthRight", QPair<QByteArray, float>("Mix::Midmouth_Right", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::MouthDown", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::MouthNarrow_Left", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::MouthNarrow_Right", 1.0f));
blendshapeMap.insert("JawOpen", QPair<QByteArray, float>("Mix::MouthOpen", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::MouthUp", 1.0f));
blendshapeMap.insert("LipsPucker", QPair<QByteArray, float>("Mix::MouthWhistle_NarrowAdjust_Left", 0.5f));
blendshapeMap.insert("LipsPucker", QPair<QByteArray, float>("Mix::MouthWhistle_NarrowAdjust_Right", 0.5f));
blendshapeMap.insert("Sneer", QPair<QByteArray, float>("Mix::NoseScrunch_Left", 0.5f));
blendshapeMap.insert("Sneer", QPair<QByteArray, float>("Mix::NoseScrunch_Right", 0.5f));
blendshapeMap.insert("MouthSmile_L", QPair<QByteArray, float>("Mix::Smile_Left", 1.0f));
blendshapeMap.insert("MouthSmile_R", QPair<QByteArray, float>("Mix::Smile_Right", 1.0f));
blendshapeMap.insert("EyeSquint_L", QPair<QByteArray, float>("Mix::Squint_Left", 1.0f));
blendshapeMap.insert("EyeSquint_R", QPair<QByteArray, float>("Mix::Squint_Right", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::UpperLipIn", 1.0f));
blendshapeMap.insert("...", QPair<QByteArray, float>("Mix::UpperLipOut", 1.0f));
blendshapeMap.insert("LipsUpperUp", QPair<QByteArray, float>("Mix::UpperLipUp_Left", 0.5f));
blendshapeMap.insert("LipsUpperUp", QPair<QByteArray, float>("Mix::UpperLipUp_Right", 0.5f));
QMultiHash<QByteArray, QPair<int, float> > channelNameMap;
for (int i = 0;; i++) {
QByteArray blendshape = FACESHIFT_BLENDSHAPES[i];
if (blendshape.isEmpty()) {
break;
}
for (QMultiHash<QByteArray, QPair<QByteArray, float> >::const_iterator it = blendshapeMap.constFind(blendshape);
it != blendshapeMap.constEnd() && it.key() == blendshape; it++) {
channelNameMap.insert(it.value().first, QPair<int, float>(i, it.value().second));
}
}
return channelNameMap;
}
static const QMultiHash<QByteArray, QPair<int, float> >& getChannelNameMap() {
static QMultiHash<QByteArray, QPair<int, float> > channelNameMap = createChannelNameMap();
return channelNameMap;
}
#endif
FaceplusReader::~FaceplusReader() {
#ifdef HAVE_FACEPLUS
if (faceplus_teardown()) {
qDebug() << "Faceplus torn down.";
}
#endif
}
void FaceplusReader::init() {
#ifdef HAVE_FACEPLUS
if (!faceplus_init("hHD")) {
qDebug() << "Failed to initialized Faceplus.";
return;
}
qDebug() << "Faceplus initialized.";
int channelCount = faceplus_output_channels_count();
_outputVector.resize(channelCount);
int maxIndex = -1;
_channelIndexMap.clear();
for (int i = 0; i < channelCount; i++) {
QByteArray name = faceplus_output_channel_name(i);
if (name == "Head_Joint::Rotation_X") {
_headRotationIndices[0] = i;
} else if (name == "Head_Joint::Rotation_Y") {
_headRotationIndices[1] = i;
} else if (name == "Head_Joint::Rotation_Z") {
_headRotationIndices[2] = i;
} else if (name == "Left_Eye_Joint::Rotation_X") {
_leftEyeRotationIndices[0] = i;
} else if (name == "Left_Eye_Joint::Rotation_Y") {
_leftEyeRotationIndices[1] = i;
} else if (name == "Right_Eye_Joint::Rotation_X") {
_rightEyeRotationIndices[0] = i;
} else if (name == "Right_Eye_Joint::Rotation_Y") {
_rightEyeRotationIndices[1] = i;
}
for (QMultiHash<QByteArray, QPair<int, float> >::const_iterator it = getChannelNameMap().constFind(name);
it != getChannelNameMap().constEnd() && it.key() == name; it++) {
_channelIndexMap.insert(i, it.value());
maxIndex = qMax(maxIndex, it.value().first);
}
}
_blendshapeCoefficients.resize(maxIndex + 1);
_referenceInitialized = false;
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
#endif
}
void FaceplusReader::shutdown() {
deleteLater();
thread()->quit();
}
void FaceplusReader::update() {
#ifdef HAVE_FACEPLUS
float x, y, rotation, scale;
if (!(faceplus_synchronous_track() && faceplus_current_face_location(&x, &y, &rotation, &scale) && !glm::isnan(x) &&
faceplus_current_output_vector(_outputVector.data()))) {
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
return;
}
if (!_referenceInitialized) {
_referenceX = x;
_referenceY = y;
_referenceInitialized = true;
}
const float TRANSLATION_SCALE = 10.0f;
glm::vec3 headTranslation((x - _referenceX) * TRANSLATION_SCALE, (y - _referenceY) * TRANSLATION_SCALE, 0.0f);
glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]),
_outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2]))));
float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) +
_outputVector.at(_rightEyeRotationIndices[0])) * -0.5f;
float estimatedEyeYaw = (_outputVector.at(_leftEyeRotationIndices[1]) +
_outputVector.at(_rightEyeRotationIndices[1])) * 0.5f;
qFill(_blendshapeCoefficients.begin(), _blendshapeCoefficients.end(), 0.0f);
for (int i = 0; i < _outputVector.size(); i++) {
for (QMultiHash<int, QPair<int, float> >::const_iterator it = _channelIndexMap.constFind(i);
it != _channelIndexMap.constEnd() && it.key() == i; it++) {
_blendshapeCoefficients[it.value().first] += _outputVector.at(i) * it.value().second;
}
}
QMetaObject::invokeMethod(Application::getInstance()->getFaceplus(), "setState", Q_ARG(const glm::vec3&, headTranslation),
Q_ARG(const glm::quat&, headRotation), Q_ARG(float, estimatedEyePitch), Q_ARG(float, estimatedEyeYaw),
Q_ARG(const QVector<float>&, _blendshapeCoefficients));
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
#endif
}
void FaceplusReader::reset() {
#ifdef HAVE_FACEPLUS
_referenceInitialized = false;
#endif
}

View file

@ -1,84 +0,0 @@
//
// Faceplus.h
// interface/src/devices
//
// Created by Andrzej Kapolka on 4/9/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Faceplus_h
#define hifi_Faceplus_h
#include <QMultiHash>
#include <QPair>
#include <QVector>
#include "FaceTracker.h"
class FaceplusReader;
/// Interface for Mixamo FacePlus.
class Faceplus : public FaceTracker {
Q_OBJECT
public:
Faceplus();
virtual ~Faceplus();
void init();
void reset();
bool isActive() const { return _active; }
Q_INVOKABLE void setState(const glm::vec3& headTranslation, const glm::quat& headRotation,
float estimatedEyePitch, float estimatedEyeYaw, const QVector<float>& blendshapeCoefficients);
public slots:
void updateEnabled();
private:
void setEnabled(bool enabled);
bool _enabled;
bool _active;
FaceplusReader* _reader;
};
Q_DECLARE_METATYPE(QVector<float>)
/// The reader object that lives in its own thread.
class FaceplusReader : public QObject {
Q_OBJECT
public:
virtual ~FaceplusReader();
Q_INVOKABLE void init();
Q_INVOKABLE void shutdown();
Q_INVOKABLE void update();
Q_INVOKABLE void reset();
private:
#ifdef HAVE_FACEPLUS
QMultiHash<int, QPair<int, float> > _channelIndexMap;
QVector<float> _outputVector;
int _headRotationIndices[3];
int _leftEyeRotationIndices[2];
int _rightEyeRotationIndices[2];
float _referenceX;
float _referenceY;
bool _referenceInitialized;
QVector<float> _blendshapeCoefficients;
#endif
};
#endif // hifi_Faceplus_h

View file

@ -15,15 +15,15 @@
#include "Joystick.h"
const float MAX_AXIS = 32768.0f;
#ifdef HAVE_SDL2
const float MAX_AXIS = 32768.0f;
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
_instanceId(instanceId),
_name(name),
_sdlGameController(sdlGameController),
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
_instanceId(instanceId),
_name(name),
_axes(QVector<float>(SDL_JoystickNumAxes(_sdlJoystick))),
_buttons(QVector<bool>(SDL_JoystickNumButtons(_sdlJoystick)))
{

View file

@ -170,7 +170,6 @@ void Visage::reset() {
void Visage::updateEnabled() {
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) &&
!Menu::getInstance()->isOptionChecked(MenuOption::Faceplus) &&
!(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) &&
Application::getInstance()->getFaceshift()->isConnectedOrConnecting()));
}

View file

@ -162,36 +162,6 @@ void renderElementProxy(EntityTreeElement* entityTreeElement) {
}
}
float EntityTreeRenderer::distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const {
glm::vec3 temp = viewFrustum.getPosition() - center;
float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp));
return distanceToVoxelCenter;
}
// TODO: This could be optimized to be a table, or something that doesn't require recalculation on every
// render call for every entity
// TODO: This is essentially the same logic used to render voxels, but since models are more detailed then voxels
// I've added a voxelToModelRatio that adjusts how much closer to a model you have to be to see it.
bool EntityTreeRenderer::shouldRenderEntity(float largestDimension, float distanceToCamera) const {
const float voxelToModelRatio = 4.0f; // must be this many times closer to a model than a voxel to see it.
float voxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
int boundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
float scale = (float)TREE_SCALE;
float visibleDistanceAtScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, voxelSizeScale) / voxelToModelRatio;
while (scale > largestDimension) {
scale /= 2.0f;
visibleDistanceAtScale /= 2.0f;
}
if (scale < largestDimension) {
visibleDistanceAtScale *= 2.0f;
}
return (distanceToCamera <= visibleDistanceAtScale);
}
void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* args) {
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
@ -287,7 +257,7 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
float distance = distanceToCamera(entityBox.calcCenter(), *args->_viewFrustum);
float distance = args->_viewFrustum->distanceToCamera(entityBox.calcCenter());
if (wantDebug) {
qDebug() << "------- renderElement() ----------";
@ -299,23 +269,28 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
qDebug() << " entityBox:" << entityItem->getAABox();
qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters";
qDebug() << " largestDimension:" << entityBox.getLargestDimension() << "in meters";
qDebug() << " shouldRender:" << shouldRenderEntity(entityBox.getLargestDimension(), distance);
qDebug() << " shouldRender:" << Menu::getInstance()->shouldRenderMesh(entityBox.getLargestDimension(), distance);
qDebug() << " in frustum:" << (args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE);
}
if (shouldRenderEntity(entityBox.getLargestDimension(), distance) &&
args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE) {
renderProxies(entityItem, args);
Glower* glower = NULL;
if (entityItem->getGlowLevel() > 0.0f) {
glower = new Glower(entityItem->getGlowLevel());
}
entityItem->render(args);
if (glower) {
delete glower;
bool outOfView = args->_viewFrustum->boxInFrustum(entityBox) == ViewFrustum::OUTSIDE;
if (!outOfView) {
bool bigEnoughToRender = Menu::getInstance()->shouldRenderMesh(entityBox.getLargestDimension(), distance);
if (bigEnoughToRender) {
renderProxies(entityItem, args);
Glower* glower = NULL;
if (entityItem->getGlowLevel() > 0.0f) {
glower = new Glower(entityItem->getGlowLevel());
}
entityItem->render(args);
args->_itemsRendered++;
if (glower) {
delete glower;
}
} else {
args->_itemsTooSmall++;
}
} else {
args->_itemsOutOfView++;

View file

@ -76,11 +76,7 @@ public:
void deleteReleasedModels();
private:
QList<Model*> _releasedModels;
float distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const;
bool shouldRenderEntity(float largestDimension, float distanceToCamera) const;
void renderProxies(const EntityItem* entity, RenderArgs* args);
};
#endif // hifi_EntityTreeRenderer_h

View file

@ -59,7 +59,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c
void RenderableModelEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableModelEntityItem::render");
PerformanceTimer perfTimer("RMEIrender");
assert(getType() == EntityTypes::Model);
bool drawAsModel = hasModel();
@ -119,7 +119,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
// TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render
// is significantly more expensive. Is there a way to call this that doesn't cost us as much?
PerformanceTimer perfTimer("model->render");
_model->render(alpha, modelRenderMode);
_model->render(alpha, modelRenderMode, args);
} else {
// if we couldn't get a model, then just draw a cube
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);

View file

@ -18,6 +18,7 @@
#include <CapsuleShape.h>
#include <GeometryUtil.h>
#include <PerfStat.h>
#include <PhysicsEntity.h>
#include <ShapeCollider.h>
#include <SphereShape.h>
@ -44,7 +45,9 @@ Model::Model(QObject* parent) :
_pupilDilation(0.0f),
_url("http://invalid.com"),
_blendNumber(0),
_appliedBlendNumber(0) {
_appliedBlendNumber(0),
_calculatedMeshBoxesValid(false),
_meshGroupsKnown(false) {
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
@ -269,6 +272,8 @@ void Model::reset() {
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f);
}
_meshGroupsKnown = false;
}
bool Model::updateGeometry() {
@ -318,6 +323,7 @@ bool Model::updateGeometry() {
deleteGeometry();
_dilatedTextures.clear();
_geometry = geometry;
_meshGroupsKnown = false;
setJointStates(newJointStates);
needToRebuild = true;
} else if (_jointStates.isEmpty()) {
@ -384,7 +390,7 @@ void Model::setJointStates(QVector<JointState> states) {
_boundingRadius = radius;
}
bool Model::render(float alpha, RenderMode mode) {
bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
// render the attachments
foreach (Model* attachment, _attachments) {
attachment->render(alpha, mode);
@ -392,6 +398,23 @@ bool Model::render(float alpha, RenderMode mode) {
if (_meshStates.isEmpty()) {
return false;
}
// if we don't have valid mesh boxes, calculate them now, this only matters in cases
// where our caller has passed RenderArgs which will include a view frustum we can cull
// against. We cache the results of these calculations so long as the model hasn't been
// simulated and the mesh hasn't changed.
if (args && !_calculatedMeshBoxesValid) {
PerformanceTimer perfTimer("calculatedMeshBoxes");
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int numberOfMeshes = geometry.meshes.size();
_calculatedMeshBoxes.resize(numberOfMeshes);
for (int i = 0; i < numberOfMeshes; i++) {
const FBXMesh& mesh = geometry.meshes.at(i);
Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents);
_calculatedMeshBoxes[i] = AABox(scaledMeshExtents);
}
_calculatedMeshBoxesValid = true;
}
// set up dilated textures on first render after load/simulate
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -402,6 +425,10 @@ bool Model::render(float alpha, RenderMode mode) {
_dilatedTextures.append(dilated);
}
}
if (!_meshGroupsKnown) {
segregateMeshGroups();
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
@ -431,11 +458,31 @@ bool Model::render(float alpha, RenderMode mode) {
mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE,
mode == DEFAULT_RENDER_MODE);
renderMeshes(mode, false);
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
//renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, book isSkinned, args);
int opaqueMeshPartsRendered = 0;
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, args);
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args);
// render translucent meshes afterwards
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true);
renderMeshes(mode, true, 0.75f);
int translucentMeshPartsRendered = 0;
const float MOSTLY_OPAQUE_THRESHOLD = 0.75f;
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, true, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args);
glDisable(GL_ALPHA_TEST);
glEnable(GL_BLEND);
@ -445,7 +492,15 @@ bool Model::render(float alpha, RenderMode mode) {
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true);
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
renderMeshes(mode, true, 0.0f);
const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f;
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, true, false, args);
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args);
}
glDepthMask(true);
@ -468,6 +523,11 @@ bool Model::render(float alpha, RenderMode mode) {
// restore all the default material settings
Application::getInstance()->setupWorldLight();
if (args) {
args->_translucentMeshPartsRendered = translucentMeshPartsRendered;
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
}
return true;
}
@ -511,6 +571,22 @@ Extents Model::getUnscaledMeshExtents() const {
return scaledExtents;
}
Extents Model::calculateScaledOffsetExtents(const Extents& extents) const {
// we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
Extents scaledOffsetExtents = { ((minimum + _offset) * _scale),
((maximum + _offset) * _scale) };
Extents rotatedExtents = scaledOffsetExtents.getRotated(_rotation);
Extents translatedExtents = { rotatedExtents.minimum + _translation,
rotatedExtents.maximum + _translation };
return translatedExtents;
}
bool Model::getJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
@ -790,6 +866,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
if (isActive() && fullUpdate) {
_calculatedMeshBoxesValid = false; // if we have to simulate, we need to assume our mesh boxes are all invalid
// check for scale to fit
if (_scaleToFit && !_scaledToFit) {
scaleToFit();
@ -1169,6 +1247,7 @@ void Model::applyNextGeometry() {
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
_baseGeometry = _nextBaseGeometry;
_geometry = _nextGeometry;
_meshGroupsKnown = false;
_nextBaseGeometry.reset();
_nextGeometry.reset();
}
@ -1200,19 +1279,353 @@ void Model::deleteGeometry() {
_blendedBlendshapeCoefficients.clear();
}
void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold) {
void Model::segregateMeshGroups() {
_meshesTranslucentTangents.clear();
_meshesTranslucent.clear();
_meshesTranslucentTangentsSpecular.clear();
_meshesTranslucentSpecular.clear();
_meshesTranslucentTangentsSkinned.clear();
_meshesTranslucentSkinned.clear();
_meshesTranslucentTangentsSpecularSkinned.clear();
_meshesTranslucentSpecularSkinned.clear();
_meshesOpaqueTangents.clear();
_meshesOpaque.clear();
_meshesOpaqueTangentsSpecular.clear();
_meshesOpaqueSpecular.clear();
_meshesOpaqueTangentsSkinned.clear();
_meshesOpaqueSkinned.clear();
_meshesOpaqueTangentsSpecularSkinned.clear();
_meshesOpaqueSpecularSkinned.clear();
_unsortedMeshesTranslucentTangents.clear();
_unsortedMeshesTranslucent.clear();
_unsortedMeshesTranslucentTangentsSpecular.clear();
_unsortedMeshesTranslucentSpecular.clear();
_unsortedMeshesTranslucentTangentsSkinned.clear();
_unsortedMeshesTranslucentSkinned.clear();
_unsortedMeshesTranslucentTangentsSpecularSkinned.clear();
_unsortedMeshesTranslucentSpecularSkinned.clear();
_unsortedMeshesOpaqueTangents.clear();
_unsortedMeshesOpaque.clear();
_unsortedMeshesOpaqueTangentsSpecular.clear();
_unsortedMeshesOpaqueSpecular.clear();
_unsortedMeshesOpaqueTangentsSkinned.clear();
_unsortedMeshesOpaqueSkinned.clear();
_unsortedMeshesOpaqueTangentsSpecularSkinned.clear();
_unsortedMeshesOpaqueSpecularSkinned.clear();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
for (int i = 0; i < networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = networkMeshes.at(i);
const FBXMesh& mesh = geometry.meshes.at(i);
const MeshState& state = _meshStates.at(i);
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size();
bool hasTangents = !mesh.tangents.isEmpty();
bool hasSpecular = mesh.hasSpecularTexture();
bool isSkinned = state.clusterMatrices.size() > 1;
QString materialID;
// create a material name from all the parts. If there's one part, this will be a single material and its
// true name. If however the mesh has multiple parts the name will be all the part's materials mashed together
// which will result in those parts being sorted away from single material parts.
QString lastPartMaterialID;
foreach(FBXMeshPart part, mesh.parts) {
if (part.materialID != lastPartMaterialID) {
materialID += part.materialID;
}
lastPartMaterialID = part.materialID;
}
const bool wantDebug = false;
if (wantDebug) {
qDebug() << "materialID:" << materialID << "parts:" << mesh.parts.size();
}
if (translucentMesh && !hasTangents && !hasSpecular && !isSkinned) {
_unsortedMeshesTranslucent.insertMulti(materialID, i);
} else if (translucentMesh && hasTangents && !hasSpecular && !isSkinned) {
_unsortedMeshesTranslucentTangents.insertMulti(materialID, i);
} else if (translucentMesh && hasTangents && hasSpecular && !isSkinned) {
_unsortedMeshesTranslucentTangentsSpecular.insertMulti(materialID, i);
} else if (translucentMesh && !hasTangents && hasSpecular && !isSkinned) {
_unsortedMeshesTranslucentSpecular.insertMulti(materialID, i);
} else if (translucentMesh && hasTangents && !hasSpecular && isSkinned) {
_unsortedMeshesTranslucentTangentsSkinned.insertMulti(materialID, i);
} else if (translucentMesh && !hasTangents && !hasSpecular && isSkinned) {
_unsortedMeshesTranslucentSkinned.insertMulti(materialID, i);
} else if (translucentMesh && hasTangents && hasSpecular && isSkinned) {
_unsortedMeshesTranslucentTangentsSpecularSkinned.insertMulti(materialID, i);
} else if (translucentMesh && !hasTangents && hasSpecular && isSkinned) {
_unsortedMeshesTranslucentSpecularSkinned.insertMulti(materialID, i);
} else if (!translucentMesh && !hasTangents && !hasSpecular && !isSkinned) {
_unsortedMeshesOpaque.insertMulti(materialID, i);
} else if (!translucentMesh && hasTangents && !hasSpecular && !isSkinned) {
_unsortedMeshesOpaqueTangents.insertMulti(materialID, i);
} else if (!translucentMesh && hasTangents && hasSpecular && !isSkinned) {
_unsortedMeshesOpaqueTangentsSpecular.insertMulti(materialID, i);
} else if (!translucentMesh && !hasTangents && hasSpecular && !isSkinned) {
_unsortedMeshesOpaqueSpecular.insertMulti(materialID, i);
} else if (!translucentMesh && hasTangents && !hasSpecular && isSkinned) {
_unsortedMeshesOpaqueTangentsSkinned.insertMulti(materialID, i);
} else if (!translucentMesh && !hasTangents && !hasSpecular && isSkinned) {
_unsortedMeshesOpaqueSkinned.insertMulti(materialID, i);
} else if (!translucentMesh && hasTangents && hasSpecular && isSkinned) {
_unsortedMeshesOpaqueTangentsSpecularSkinned.insertMulti(materialID, i);
} else if (!translucentMesh && !hasTangents && hasSpecular && isSkinned) {
_unsortedMeshesOpaqueSpecularSkinned.insertMulti(materialID, i);
} else {
qDebug() << "unexpected!!! this mesh didn't fall into any or our groups???";
}
}
foreach(int i, _unsortedMeshesTranslucent) {
_meshesTranslucent.append(i);
}
foreach(int i, _unsortedMeshesTranslucentTangents) {
_meshesTranslucentTangents.append(i);
}
foreach(int i, _unsortedMeshesTranslucentTangentsSpecular) {
_meshesTranslucentTangentsSpecular.append(i);
}
foreach(int i, _unsortedMeshesTranslucentSpecular) {
_meshesTranslucentSpecular.append(i);
}
foreach(int i, _unsortedMeshesTranslucentSkinned) {
_meshesTranslucentSkinned.append(i);
}
foreach(int i, _unsortedMeshesTranslucentTangentsSkinned) {
_meshesTranslucentTangentsSkinned.append(i);
}
foreach(int i, _unsortedMeshesTranslucentTangentsSpecularSkinned) {
_meshesTranslucentTangentsSpecularSkinned.append(i);
}
foreach(int i, _unsortedMeshesTranslucentSpecularSkinned) {
_meshesTranslucentSpecularSkinned.append(i);
}
foreach(int i, _unsortedMeshesOpaque) {
_meshesOpaque.append(i);
}
foreach(int i, _unsortedMeshesOpaqueTangents) {
_meshesOpaqueTangents.append(i);
}
foreach(int i, _unsortedMeshesOpaqueTangentsSpecular) {
_meshesOpaqueTangentsSpecular.append(i);
}
foreach(int i, _unsortedMeshesOpaqueSpecular) {
_meshesOpaqueSpecular.append(i);
}
foreach(int i, _unsortedMeshesOpaqueSkinned) {
_meshesOpaqueSkinned.append(i);
}
foreach(int i, _unsortedMeshesOpaqueTangentsSkinned) {
_meshesOpaqueTangentsSkinned.append(i);
}
foreach(int i, _unsortedMeshesOpaqueTangentsSpecularSkinned) {
_meshesOpaqueTangentsSpecularSkinned.append(i);
}
foreach(int i, _unsortedMeshesOpaqueSpecularSkinned) {
_meshesOpaqueSpecularSkinned.append(i);
}
_unsortedMeshesTranslucentTangents.clear();
_unsortedMeshesTranslucent.clear();
_unsortedMeshesTranslucentTangentsSpecular.clear();
_unsortedMeshesTranslucentSpecular.clear();
_unsortedMeshesTranslucentTangentsSkinned.clear();
_unsortedMeshesTranslucentSkinned.clear();
_unsortedMeshesTranslucentTangentsSpecularSkinned.clear();
_unsortedMeshesTranslucentSpecularSkinned.clear();
_unsortedMeshesOpaqueTangents.clear();
_unsortedMeshesOpaque.clear();
_unsortedMeshesOpaqueTangentsSpecular.clear();
_unsortedMeshesOpaqueSpecular.clear();
_unsortedMeshesOpaqueTangentsSkinned.clear();
_unsortedMeshesOpaqueSkinned.clear();
_unsortedMeshesOpaqueTangentsSpecularSkinned.clear();
_unsortedMeshesOpaqueSpecularSkinned.clear();
_meshGroupsKnown = true;
}
int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold,
bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args) {
bool dontCullOutOfViewMeshParts = Menu::getInstance()->isOptionChecked(MenuOption::DontCullOutOfViewMeshParts);
bool cullTooSmallMeshParts = !Menu::getInstance()->isOptionChecked(MenuOption::DontCullTooSmallMeshParts);
bool dontReduceMaterialSwitches = Menu::getInstance()->isOptionChecked(MenuOption::DontReduceMaterialSwitches);
QString lastMaterialID;
int meshPartsRendered = 0;
updateVisibleJointStates();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
// depending on which parameters we were called with, pick the correct mesh group to render
QVector<int>* whichList = NULL;
if (translucent && !hasTangents && !hasSpecular && !isSkinned) {
whichList = &_meshesTranslucent;
} else if (translucent && hasTangents && !hasSpecular && !isSkinned) {
whichList = &_meshesTranslucentTangents;
} else if (translucent && hasTangents && hasSpecular && !isSkinned) {
whichList = &_meshesTranslucentTangentsSpecular;
} else if (translucent && !hasTangents && hasSpecular && !isSkinned) {
whichList = &_meshesTranslucentSpecular;
} else if (translucent && hasTangents && !hasSpecular && isSkinned) {
whichList = &_meshesTranslucentTangentsSkinned;
} else if (translucent && !hasTangents && !hasSpecular && isSkinned) {
whichList = &_meshesTranslucentSkinned;
} else if (translucent && hasTangents && hasSpecular && isSkinned) {
whichList = &_meshesTranslucentTangentsSpecularSkinned;
} else if (translucent && !hasTangents && hasSpecular && isSkinned) {
whichList = &_meshesTranslucentSpecularSkinned;
} else if (!translucent && !hasTangents && !hasSpecular && !isSkinned) {
whichList = &_meshesOpaque;
} else if (!translucent && hasTangents && !hasSpecular && !isSkinned) {
whichList = &_meshesOpaqueTangents;
} else if (!translucent && hasTangents && hasSpecular && !isSkinned) {
whichList = &_meshesOpaqueTangentsSpecular;
} else if (!translucent && !hasTangents && hasSpecular && !isSkinned) {
whichList = &_meshesOpaqueSpecular;
} else if (!translucent && hasTangents && !hasSpecular && isSkinned) {
whichList = &_meshesOpaqueTangentsSkinned;
} else if (!translucent && !hasTangents && !hasSpecular && isSkinned) {
whichList = &_meshesOpaqueSkinned;
} else if (!translucent && hasTangents && hasSpecular && isSkinned) {
whichList = &_meshesOpaqueTangentsSpecularSkinned;
} else if (!translucent && !hasTangents && hasSpecular && isSkinned) {
whichList = &_meshesOpaqueSpecularSkinned;
} else {
qDebug() << "unexpected!!! this mesh didn't fall into any or our groups???";
}
for (int i = 0; i < networkMeshes.size(); i++) {
if (!whichList) {
qDebug() << "unexpected!!! we don't know which list of meshes to render...";
return 0;
}
QVector<int>& list = *whichList;
ProgramObject* program = &_program;
Locations* locations = &_locations;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
GLenum specularTextureUnit = 0;
if (mode == SHADOW_RENDER_MODE) {
program = &_shadowProgram;
skinProgram = &_skinShadowProgram;
skinLocations = &_skinShadowLocations;
} else if (translucent && alphaThreshold == 0.0f) {
program = &_translucentProgram;
locations = &_translucentLocations;
skinProgram = &_skinTranslucentProgram;
skinLocations = &_skinTranslucentLocations;
} else if (hasTangents) {
if (hasSpecular) {
program = &_normalSpecularMapProgram;
locations = &_normalSpecularMapLocations;
skinProgram = &_skinNormalSpecularMapProgram;
skinLocations = &_skinNormalSpecularMapLocations;
specularTextureUnit = GL_TEXTURE2;
} else {
program = &_normalMapProgram;
locations = &_normalMapLocations;
skinProgram = &_skinNormalMapProgram;
skinLocations = &_skinNormalMapLocations;
}
} else if (hasSpecular) {
program = &_specularMapProgram;
locations = &_specularMapLocations;
skinProgram = &_skinSpecularMapProgram;
skinLocations = &_skinSpecularMapLocations;
specularTextureUnit = GL_TEXTURE1;
}
ProgramObject* activeProgram = program;
Locations* activeLocations = locations;
if (isSkinned) {
skinProgram->bind();
activeProgram = skinProgram;
activeLocations = skinLocations;
} else {
program->bind();
}
activeProgram->setUniformValue(activeLocations->alphaThreshold, alphaThreshold);
// i is the "index" from the original networkMeshes QVector...
foreach (int i, list) {
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
// to false to rebuild out mesh groups.
if (i < 0 || i >= networkMeshes.size() || i > geometry.meshes.size()) {
_meshGroupsKnown = false; // regenerate these lists next time around.
continue;
}
// exit early if the translucency doesn't match what we're drawing
const NetworkMesh& networkMesh = networkMeshes.at(i);
const FBXMesh& mesh = geometry.meshes.at(i);
if (translucent ? (networkMesh.getTranslucentPartCount(mesh) == 0) :
(networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size())) {
continue;
}
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
int vertexCount = mesh.vertices.size();
@ -1221,54 +1634,40 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
continue;
}
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
ProgramObject* program = &_program;
Locations* locations = &_locations;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations;
GLenum specularTextureUnit = 0;
if (mode == SHADOW_RENDER_MODE) {
program = &_shadowProgram;
skinProgram = &_skinShadowProgram;
skinLocations = &_skinShadowLocations;
} else if (translucent && alphaThreshold == 0.0f) {
program = &_translucentProgram;
locations = &_translucentLocations;
skinProgram = &_skinTranslucentProgram;
skinLocations = &_skinTranslucentLocations;
} else if (!mesh.tangents.isEmpty()) {
if (mesh.hasSpecularTexture()) {
program = &_normalSpecularMapProgram;
locations = &_normalSpecularMapLocations;
skinProgram = &_skinNormalSpecularMapProgram;
skinLocations = &_skinNormalSpecularMapLocations;
specularTextureUnit = GL_TEXTURE2;
} else {
program = &_normalMapProgram;
locations = &_normalMapLocations;
skinProgram = &_skinNormalMapProgram;
skinLocations = &_skinNormalMapLocations;
// if we got here, then check to see if this mesh is in view
if (args) {
bool shouldRender = true;
args->_meshesConsidered++;
if (args->_viewFrustum) {
shouldRender = dontCullOutOfViewMeshParts ||
args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
if (shouldRender && cullTooSmallMeshParts) {
float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
shouldRender = Menu::getInstance()->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
distance);
if (!shouldRender) {
args->_meshesTooSmall++;
}
} else {
args->_meshesOutOfView++;
}
}
if (shouldRender) {
args->_meshesRendered++;
} else {
continue; // skip this mesh
}
} else if (mesh.hasSpecularTexture()) {
program = &_specularMapProgram;
locations = &_specularMapLocations;
skinProgram = &_skinSpecularMapProgram;
skinLocations = &_skinSpecularMapLocations;
specularTextureUnit = GL_TEXTURE1;
}
const MeshState& state = _meshStates.at(i);
ProgramObject* activeProgram = program;
Locations* activeLocations = locations;
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
const MeshState& state = _meshStates.at(i);
if (state.clusterMatrices.size() > 1) {
skinProgram->bind();
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
(const float*)state.clusterMatrices.constData());
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
@ -1279,16 +1678,10 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
offset + vertexCount * sizeof(glm::vec4), 4);
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
activeProgram = skinProgram;
activeLocations = skinLocations;
} else {
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
program->bind();
}
activeProgram->setUniformValue(activeLocations->alphaThreshold, alphaThreshold);
if (mesh.blendshapes.isEmpty()) {
if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) {
activeProgram->setAttributeBuffer(activeLocations->tangent, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
@ -1333,45 +1726,74 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
glBindTexture(GL_TEXTURE_2D, 0);
} else {
glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity);
if (!(translucent && alphaThreshold == 0.0f)) {
glAlphaFunc(GL_EQUAL, diffuse.a = Application::getInstance()->getGlowEffect()->getIntensity());
}
glm::vec4 specular = glm::vec4(part.specularColor, 1.0f);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
if (dontReduceMaterialSwitches || lastMaterialID != part.materialID) {
const bool wantDebug = false;
if (wantDebug) {
qDebug() << "Material Changed ---------------------------------------------";
qDebug() << "part INDEX:" << j;
qDebug() << "NEW part.materialID:" << part.materialID;
}
glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity);
if (!(translucent && alphaThreshold == 0.0f)) {
glAlphaFunc(GL_EQUAL, diffuse.a = Application::getInstance()->getGlowEffect()->getIntensity());
}
glm::vec4 specular = glm::vec4(part.specularColor, 1.0f);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
Texture* diffuseMap = networkPart.diffuseTexture.data();
if (mesh.isEye && diffuseMap) {
diffuseMap = (_dilatedTextures[i][j] =
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
}
glBindTexture(GL_TEXTURE_2D, !diffuseMap ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
Texture* diffuseMap = networkPart.diffuseTexture.data();
if (mesh.isEye && diffuseMap) {
diffuseMap = (_dilatedTextures[i][j] =
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
}
glBindTexture(GL_TEXTURE_2D, !diffuseMap ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1);
Texture* normalMap = networkPart.normalTexture.data();
glBindTexture(GL_TEXTURE_2D, !normalMap ?
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
glActiveTexture(GL_TEXTURE0);
}
if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1);
Texture* normalMap = networkPart.normalTexture.data();
glBindTexture(GL_TEXTURE_2D, !normalMap ?
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
glActiveTexture(GL_TEXTURE0);
}
if (specularTextureUnit) {
glActiveTexture(specularTextureUnit);
Texture* specularMap = networkPart.specularTexture.data();
glBindTexture(GL_TEXTURE_2D, !specularMap ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID());
glActiveTexture(GL_TEXTURE0);
if (specularTextureUnit) {
glActiveTexture(specularTextureUnit);
Texture* specularMap = networkPart.specularTexture.data();
glBindTexture(GL_TEXTURE_2D, !specularMap ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID());
glActiveTexture(GL_TEXTURE0);
}
if (args) {
args->_materialSwitches++;
}
}
lastMaterialID = part.materialID;
}
meshPartsRendered++;
if (part.quadIndices.size() > 0) {
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
offset += part.quadIndices.size() * sizeof(int);
}
if (part.triangleIndices.size() > 0) {
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)offset);
offset += part.triangleIndices.size() * sizeof(int);
}
if (args) {
const int INDICES_PER_TRIANGLE = 3;
const int INDICES_PER_QUAD = 4;
args->_trianglesRendered += part.triangleIndices.size() / INDICES_PER_TRIANGLE;
args->_quadsRendered += part.quadIndices.size() / INDICES_PER_QUAD;
}
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
offset += part.quadIndices.size() * sizeof(int);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)offset);
offset += part.triangleIndices.size() * sizeof(int);
}
if (!mesh.colors.isEmpty()) {
@ -1401,8 +1823,10 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
}
glPopMatrix();
activeProgram->release();
}
activeProgram->release();
return meshPartsRendered;
}
void AnimationHandle::setURL(const QUrl& url) {

View file

@ -16,9 +16,9 @@
#include <QObject>
#include <QUrl>
#include <PhysicsEntity.h>
#include <AABox.h>
#include <AnimationCache.h>
#include <PhysicsEntity.h>
#include "GeometryCache.h"
#include "InterfaceConfig.h"
@ -30,6 +30,8 @@ class QScriptEngine;
class AnimationHandle;
class Shape;
class RenderArgs;
class ViewFrustum;
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
@ -84,7 +86,7 @@ public:
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE };
bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE);
bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL);
/// Sets the URL of the model to render.
/// \param fallback the URL of a fallback model to render if the requested model fails to load
@ -107,6 +109,9 @@ public:
/// Returns the unscaled extents of the model's mesh
Extents getUnscaledMeshExtents() const;
/// Returns the scaled equivalent of some extents in model space.
Extents calculateScaledOffsetExtents(const Extents& extents) const;
/// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
@ -247,7 +252,7 @@ private:
void applyNextGeometry();
void deleteGeometry();
void renderMeshes(RenderMode mode, bool translucent, float alphaThreshold = 0.5f);
int renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL);
QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms();
@ -325,6 +330,54 @@ private:
static SkinLocations _skinTranslucentLocations;
static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1);
QVector<AABox> _calculatedMeshBoxes;
bool _calculatedMeshBoxesValid;
void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes
bool _meshGroupsKnown;
QMap<QString, int> _unsortedMeshesTranslucent;
QMap<QString, int> _unsortedMeshesTranslucentTangents;
QMap<QString, int> _unsortedMeshesTranslucentTangentsSpecular;
QMap<QString, int> _unsortedMeshesTranslucentSpecular;
QMap<QString, int> _unsortedMeshesTranslucentSkinned;
QMap<QString, int> _unsortedMeshesTranslucentTangentsSkinned;
QMap<QString, int> _unsortedMeshesTranslucentTangentsSpecularSkinned;
QMap<QString, int> _unsortedMeshesTranslucentSpecularSkinned;
QMap<QString, int> _unsortedMeshesOpaque;
QMap<QString, int> _unsortedMeshesOpaqueTangents;
QMap<QString, int> _unsortedMeshesOpaqueTangentsSpecular;
QMap<QString, int> _unsortedMeshesOpaqueSpecular;
QMap<QString, int> _unsortedMeshesOpaqueSkinned;
QMap<QString, int> _unsortedMeshesOpaqueTangentsSkinned;
QMap<QString, int> _unsortedMeshesOpaqueTangentsSpecularSkinned;
QMap<QString, int> _unsortedMeshesOpaqueSpecularSkinned;
QVector<int> _meshesTranslucent;
QVector<int> _meshesTranslucentTangents;
QVector<int> _meshesTranslucentTangentsSpecular;
QVector<int> _meshesTranslucentSpecular;
QVector<int> _meshesTranslucentSkinned;
QVector<int> _meshesTranslucentTangentsSkinned;
QVector<int> _meshesTranslucentTangentsSpecularSkinned;
QVector<int> _meshesTranslucentSpecularSkinned;
QVector<int> _meshesOpaque;
QVector<int> _meshesOpaqueTangents;
QVector<int> _meshesOpaqueTangentsSpecular;
QVector<int> _meshesOpaqueSpecular;
QVector<int> _meshesOpaqueSkinned;
QVector<int> _meshesOpaqueTangentsSkinned;
QVector<int> _meshesOpaqueTangentsSpecularSkinned;
QVector<int> _meshesOpaqueSpecularSkinned;
};
Q_DECLARE_METATYPE(QPointer<Model>)

View file

@ -1,168 +0,0 @@
//
// OAuthWebViewHandler.cpp
// interface/src/ui
//
// Created by Stephen Birarda on 2014-05-01.
// 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 <qwebview.h>
#include <qurlquery.h>
#include <AccountManager.h>
#include "Application.h"
#include "OAuthWebViewHandler.h"
OAuthWebViewHandler& OAuthWebViewHandler::getInstance() {
static OAuthWebViewHandler sharedInstance;
return sharedInstance;
}
OAuthWebViewHandler::OAuthWebViewHandler() :
_activeWebView(NULL),
_webViewRedisplayTimer(),
_lastAuthorizationURL()
{
addHighFidelityRootCAToSSLConfig();
}
const char HIGH_FIDELITY_CA[] = "-----BEGIN CERTIFICATE-----\n"
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
"YXRpb25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEW\n"
"E29wc0BoaWdoZmlkZWxpdHkuaW8wHhcNMTQwMzI4MjIzMzM1WhcNMjQwMzI1MjIz\n"
"MzM1WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\n"
"BAcTDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoTEkhpZ2ggRmlkZWxpdHksIEluYzET\n"
"MBEGA1UECxMKT3BlcmF0aW9uczEYMBYGA1UEAxMPaGlnaGZpZGVsaXR5LmlvMSIw\n"
"IAYJKoZIhvcNAQkBFhNvcHNAaGlnaGZpZGVsaXR5LmlvMIGfMA0GCSqGSIb3DQEB\n"
"AQUAA4GNADCBiQKBgQDyo1euYiPPEdnvDZnIjWrrP230qUKMSj8SWoIkbTJF2hE8\n"
"2eP3YOgbgSGBzZ8EJBxIOuNmj9g9Eg6691hIKFqy5W0BXO38P04Gg+pVBvpHFGBi\n"
"wpqGbfsjaUDuYmBeJRcMO0XYkLCRQG+lAQNHoFDdItWAJfC3FwtP3OCDnz8cNwID\n"
"AQABo4IBEzCCAQ8wHQYDVR0OBBYEFCSv2kmiGg6VFMnxXzLDNP304cPAMIHfBgNV\n"
"HSMEgdcwgdSAFCSv2kmiGg6VFMnxXzLDNP304cPAoYGwpIGtMIGqMQswCQYDVQQG\n"
"EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\n"
"bzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVyYXRp\n"
"b25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEWE29w\n"
"c0BoaWdoZmlkZWxpdHkuaW+CCQDZX0ZEQ/QPGzAMBgNVHRMEBTADAQH/MA0GCSqG\n"
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
"-----END CERTIFICATE-----\n";
void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() {
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
// add the High Fidelity root CA to the list of trusted CA certificates
QByteArray highFidelityCACertificate(HIGH_FIDELITY_CA, sizeof(HIGH_FIDELITY_CA));
sslConfig.setCaCertificates(sslConfig.caCertificates() + QSslCertificate::fromData(highFidelityCACertificate));
// set the modified configuration
QSslConfiguration::setDefaultConfiguration(sslConfig);
}
const int WEB_VIEW_REDISPLAY_ELAPSED_MSECS = 5 * 1000;
void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authorizationURL) {
if (!_activeWebView) {
if (!_lastAuthorizationURL.isEmpty()) {
if (_lastAuthorizationURL.host() == authorizationURL.host()
&& _webViewRedisplayTimer.elapsed() < WEB_VIEW_REDISPLAY_ELAPSED_MSECS) {
// this would be re-displaying an OAuth dialog for the same auth URL inside of the redisplay ms
// so return instead
return;
}
}
_lastAuthorizationURL = authorizationURL;
_activeWebView = new QWebView;
// keep the window on top and delete it when it closes
_activeWebView->setWindowFlags(Qt::Sheet);
_activeWebView->setAttribute(Qt::WA_DeleteOnClose);
qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString();
AccountManager& accountManager = AccountManager::getInstance();
QUrl codedAuthorizationURL = authorizationURL;
// check if we have an access token for this host - if so we can bypass login by adding it to the URL
if (accountManager.getAuthURL().host() == authorizationURL.host()
&& accountManager.hasValidAccessToken()) {
const QString ACCESS_TOKEN_QUERY_STRING_KEY = "access_token";
QUrlQuery authQuery(codedAuthorizationURL);
authQuery.addQueryItem(ACCESS_TOKEN_QUERY_STRING_KEY, accountManager.getAccountInfo().getAccessToken().token);
codedAuthorizationURL.setQuery(authQuery);
}
connect(_activeWebView.data(), &QWebView::urlChanged, this, &OAuthWebViewHandler::handleURLChanged);
_activeWebView->load(codedAuthorizationURL);
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors,
this, &OAuthWebViewHandler::handleSSLErrors);
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::finished,
this, &OAuthWebViewHandler::handleReplyFinished);
connect(_activeWebView.data(), &QWebView::loadFinished, this, &OAuthWebViewHandler::handleLoadFinished);
// connect to the destroyed signal so after the web view closes we can start a timer
connect(_activeWebView.data(), &QWebView::destroyed, this, &OAuthWebViewHandler::handleWebViewDestroyed);
}
}
void OAuthWebViewHandler::handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList) {
qDebug() << "SSL Errors:" << errorList;
}
void OAuthWebViewHandler::handleLoadFinished(bool success) {
if (success && _activeWebView->url().host() == NodeList::getInstance()->getDomainHandler().getHostname()) {
qDebug() << "OAuth authorization code passed successfully to domain-server.";
// grab the UUID that is set as the state parameter in the auth URL
// since that is our new session UUID
QUrlQuery authQuery(_activeWebView->url());
const QString AUTH_STATE_QUERY_KEY = "state";
NodeList::getInstance()->setSessionUUID(QUuid(authQuery.queryItemValue(AUTH_STATE_QUERY_KEY)));
_activeWebView->close();
_activeWebView = NULL;
}
}
void OAuthWebViewHandler::handleReplyFinished(QNetworkReply* reply) {
if (_activeWebView && reply->error() != QNetworkReply::NoError) {
qDebug() << "Error loading" << reply->url() << "-" << reply->errorString();
_activeWebView->close();
}
}
void OAuthWebViewHandler::handleWebViewDestroyed(QObject* destroyedObject) {
_webViewRedisplayTimer.restart();
}
void OAuthWebViewHandler::handleURLChanged(const QUrl& url) {
// check if this is the authorization screen - if it is then we need to show the OAuthWebViewHandler
const QString ACCESS_TOKEN_URL_REGEX_STRING = "redirect_uri=[\\w:\\/\\.]+&access_token=";
QRegExp accessTokenRegex(ACCESS_TOKEN_URL_REGEX_STRING);
if (accessTokenRegex.indexIn(url.toString()) != -1) {
_activeWebView->show();
} else if (url.toString() == DEFAULT_NODE_AUTH_URL.toString() + "/login") {
// this is a login request - we're going to close the webview and signal the AccountManager that we need a login
qDebug() << "data-server replied with login request. Signalling that login is required to proceed with OAuth.";
_activeWebView->close();
AccountManager::getInstance().checkAndSignalForAccessToken();
}
}

View file

@ -1,43 +0,0 @@
//
// OAuthWebviewHandler.h
// interface/src/ui
//
// Created by Stephen Birarda on 2014-05-01.
// 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_OAuthWebviewHandler_h
#define hifi_OAuthWebviewHandler_h
#include <QtCore/QUrl>
class QWebView;
class OAuthWebViewHandler : public QObject {
Q_OBJECT
public:
OAuthWebViewHandler();
static OAuthWebViewHandler& getInstance();
static void addHighFidelityRootCAToSSLConfig();
void clearLastAuthorizationURL() { _lastAuthorizationURL = QUrl(); }
public slots:
void displayWebviewForAuthorizationURL(const QUrl& authorizationURL);
private slots:
void handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList);
void handleLoadFinished(bool success);
void handleReplyFinished(QNetworkReply* reply);
void handleWebViewDestroyed(QObject* destroyedObject);
void handleURLChanged(const QUrl& url);
private:
QPointer<QWebView> _activeWebView;
QElapsedTimer _webViewRedisplayTimer;
QUrl _lastAuthorizationURL;
};
#endif // hifi_OAuthWebviewHandler_h

View file

@ -221,8 +221,30 @@ void Stats::display(
int totalServers = NodeList::getInstance()->size();
lines = _expanded ? 5 : 3;
drawBackground(backgroundColor, horizontalOffset, 0, _generalStatsWidth, lines * STATS_PELS_PER_LINE + 10);
int columnOneWidth = _generalStatsWidth;
PerformanceTimer::tallyAllTimerRecords(); // do this even if we're not displaying them, so they don't stack up
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
columnOneWidth = _generalStatsWidth + _pingStatsWidth + _geoStatsWidth; // make it 3 columns wide...
// we will also include room for 1 line per timing record and a header of 4 lines
lines += 4;
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
while (i.hasNext()) {
i.next();
if (includeTimingRecord(i.key())) {
lines++;
}
}
}
drawBackground(backgroundColor, horizontalOffset, 0, columnOneWidth, lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;
int columnOneHorizontalOffset = horizontalOffset;
char serverNodes[30];
sprintf(serverNodes, "Servers: %d", totalServers);
@ -249,6 +271,46 @@ void Stats::display(
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond, color);
}
// TODO: the display of these timing details should all be moved to JavaScript
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
// Timing details...
const int TIMER_OUTPUT_LINE_LENGTH = 1000;
char perfLine[TIMER_OUTPUT_LINE_LENGTH];
verticalOffset += STATS_PELS_PER_LINE * 4; // skip 4 lines to be under the other columns
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font,
"-------------------------------------------------------- Function "
"------------------------------------------------------- --msecs- -calls--", color);
// First iterate all the records, and for the ones that should be included, insert them into
// a new Map sorted by average time...
QMap<float, QString> sortedRecords;
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
while (i.hasNext()) {
i.next();
if (includeTimingRecord(i.key())) {
float averageTime = (float)i.value().getMovingAverage() / (float)USECS_PER_MSEC;
sortedRecords.insertMulti(averageTime, i.key());
}
}
QMapIterator<float, QString> j(sortedRecords);
j.toBack();
while (j.hasPrevious()) {
j.previous();
QString functionName = j.value();
const PerformanceTimerRecord& record = allRecords.value(functionName);
sprintf(perfLine, "%120s: %8.4f [%6llu]", qPrintable(functionName),
(float)record.getMovingAverage() / (float)USECS_PER_MSEC,
record.getCount());
verticalOffset += STATS_PELS_PER_LINE;
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, perfLine, color);
}
}
verticalOffset = 0;
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1;
@ -283,7 +345,11 @@ void Stats::display(
}
lines = _expanded ? 4 : 3;
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
// only draw our background if column one didn't draw a wide background
if (columnOneWidth == _generalStatsWidth) {
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
}
horizontalOffset += 5;
@ -319,7 +385,9 @@ void Stats::display(
lines = _expanded ? 8 : 3;
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
if (columnOneWidth == _generalStatsWidth) {
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
}
horizontalOffset += 5;
char avatarPosition[200];
@ -391,39 +459,63 @@ void Stats::display(
VoxelSystem* voxels = Application::getInstance()->getVoxels();
lines = _expanded ? 11 : 3;
lines = _expanded ? 14 : 3;
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info
lines += 10; // spatial audio processing adds 1 spacing line and 8 extra lines of info
}
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
// we will also include room for 1 line per timing record and a header
lines += 1;
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
while (i.hasNext()) {
i.next();
if (includeTimingRecord(i.key())) {
lines++;
}
}
}
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset,
lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;
// Model/Entity render details
EntityTreeRenderer* entities = Application::getInstance()->getEntities();
voxelStats.str("");
voxelStats << "Entity Items rendered: " << entities->getItemsRendered()
<< " / Out of view:" << entities->getItemsOutOfView()
<< " / Too small:" << entities->getItemsTooSmall();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
if (_expanded) {
voxelStats.str("");
voxelStats << " Meshes rendered: " << entities->getMeshesRendered()
<< " / Out of view:" << entities->getMeshesOutOfView()
<< " / Too small:" << entities->getMeshesTooSmall();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
voxelStats.str("");
voxelStats << " Triangles: " << entities->getTrianglesRendered()
<< " / Quads:" << entities->getQuadsRendered()
<< " / Material Switches:" << entities->getMaterialSwitches();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
voxelStats.str("");
voxelStats << " Mesh Parts Rendered Opaque: " << entities->getOpaqueMeshPartsRendered()
<< " / Translucent:" << entities->getTranslucentMeshPartsRendered();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
}
voxelStats.str("");
voxelStats.precision(4);
voxelStats << "Voxels Drawn: " << voxels->getVoxelsWritten() / 1000.f << "K " <<
"Abandoned: " << voxels->getAbandonedVoxels() / 1000.f << "K ";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
if (_expanded) {
// Local Voxel Memory Usage
voxelStats.str("");
voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB";
voxelStats << " Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
voxelStats.str("");
voxelStats <<
"Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB / " <<
" Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB / " <<
"VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB";
if (voxels->hasVoxelMemoryUsageGPU()) {
voxelStats << " / GPU: " << voxels->getVoxelMemoryUsageGPU() / 1000000.f << "MB";
@ -434,18 +526,11 @@ void Stats::display(
// Voxel Rendering
voxelStats.str("");
voxelStats.precision(4);
voxelStats << "Voxel Rendering Slots Max: " << voxels->getMaxVoxels() / 1000.f << "K";
voxelStats << " Voxel Rendering Slots Max: " << voxels->getMaxVoxels() / 1000.f << "K";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
}
voxelStats.str("");
voxelStats.precision(4);
voxelStats << "Drawn: " << voxels->getVoxelsWritten() / 1000.f << "K " <<
"Abandoned: " << voxels->getAbandonedVoxels() / 1000.f << "K ";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
// iterate all the current voxel stats, and list their sending modes, and total voxel counts
std::stringstream sendingMode("");
sendingMode << "Octree Sending Mode: [";
@ -516,44 +601,44 @@ void Stats::display(
}
QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' ');
unsigned long localTotal = VoxelTreeElement::getNodeCount();
QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
// Server Voxels
voxelStats.str("");
voxelStats << "Server voxels: " << qPrintable(serversTotalString);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
if (_expanded) {
QString serversInternalString = locale.toString((uint)totalInternal);
QString serversLeavesString = locale.toString((uint)totalLeaves);
// Server Octree Elements
if (!_expanded) {
voxelStats.str("");
voxelStats <<
"Internal: " << qPrintable(serversInternalString) << " " <<
"Leaves: " << qPrintable(serversLeavesString) << "";
voxelStats << "Octree Elements Server: " << qPrintable(serversTotalString)
<< " Local:" << qPrintable(localTotalString);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
}
unsigned long localTotal = VoxelTreeElement::getNodeCount();
QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
// Local Voxels
voxelStats.str("");
voxelStats << "Local voxels: " << qPrintable(localTotalString);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
if (_expanded) {
voxelStats.str("");
voxelStats << "Octree Elements -";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
QString serversInternalString = locale.toString((uint)totalInternal);
QString serversLeavesString = locale.toString((uint)totalLeaves);
voxelStats.str("");
voxelStats << " Server: " << qPrintable(serversTotalString) <<
" Internal: " << qPrintable(serversInternalString) <<
" Leaves: " << qPrintable(serversLeavesString);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
// Local Voxels
unsigned long localInternal = VoxelTreeElement::getInternalNodeCount();
unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount();
QString localInternalString = locale.toString((uint)localInternal);
QString localLeavesString = locale.toString((uint)localLeaves);
voxelStats.str("");
voxelStats <<
"Internal: " << qPrintable(localInternalString) << " " <<
"Leaves: " << qPrintable(localLeavesString) << "";
voxelStats << " Local: " << qPrintable(serversTotalString) <<
" Internal: " << qPrintable(localInternalString) <<
" Leaves: " << qPrintable(localLeavesString) << "";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
}
@ -567,32 +652,6 @@ void Stats::display(
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
}
PerformanceTimer::tallyAllTimerRecords();
// TODO: the display of these timing details should all be moved to JavaScript
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
// Timing details...
const int TIMER_OUTPUT_LINE_LENGTH = 300;
char perfLine[TIMER_OUTPUT_LINE_LENGTH];
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font,
"--------------------- Function -------------------- --msecs- -calls--", color);
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
while (i.hasNext()) {
i.next();
if (includeTimingRecord(i.key())) {
sprintf(perfLine, "%50s: %8.4f [%6llu]", qPrintable(i.key()),
(float)i.value().getMovingAverage() / (float)USECS_PER_MSEC,
i.value().getCount());
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, perfLine, color);
}
}
}
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
verticalOffset += STATS_PELS_PER_LINE; // space one line...

View file

@ -13,6 +13,5 @@
#define hifi_world_h
const float GRAVITY_EARTH = 9.80665f;
const float EDGE_SIZE_GROUND_PLANE = 20.f;
#endif // hifi_world_h

View file

@ -659,6 +659,7 @@ public:
glm::vec3 emissive;
float shininess;
float opacity;
QString id;
};
class Cluster {
@ -1319,7 +1320,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
}
}
materials.insert(getID(object.properties), material);
material.id = getID(object.properties);
materials.insert(material.id, material);
} else if (object.name == "Deformer") {
if (object.properties.last() == "Cluster") {
@ -1621,6 +1623,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (!specularTexture.filename.isNull()) {
part.specularTexture = specularTexture;
}
part.materialID = material.id;
}
}
materialIndex++;

View file

@ -115,6 +115,8 @@ public:
FBXTexture diffuseTexture;
FBXTexture normalTexture;
FBXTexture specularTexture;
QString materialID;
};
/// A single mesh (with optional blendshapes) extracted from an FBX document.

View file

@ -10,5 +10,19 @@ if (WIN32)
target_link_libraries(${TARGET_NAME} ws2_32.lib)
endif ()
# find OpenSSL
find_package(OpenSSL REQUIRED)
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
"\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.")
endif ()
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
# append OpenSSL to our list of libraries to link
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${OPENSSL_LIBRARIES}")
# call macro to link our dependencies and bubble them up via a property on our target
link_shared_dependencies()

View file

@ -19,9 +19,11 @@
#include <QtCore/QUrlQuery>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkRequest>
#include <qthread.h>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "RSAKeypairGenerator.h"
#include "AccountManager.h"
@ -144,6 +146,12 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
} else {
requestProfile();
}
// if we don't have a private key in settings we should generate a new keypair
if (!_accountInfo.hasPrivateKey()) {
qDebug() << "No private key present - generating a new key-pair.";
generateNewKeypair();
}
}
}
}
@ -280,14 +288,12 @@ void AccountManager::processReply() {
}
void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
if (callbackParams.jsonCallbackReceiver) {
// invoke the right method on the callback receiver
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod),
Q_ARG(const QJsonObject&, jsonResponse.object()));
Q_ARG(QNetworkReply&, *requestReply));
// remove the related reply-callback group from the map
_pendingCallbackMap.remove(requestReply);
@ -295,7 +301,7 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received JSON response from data-server that has no matching callback.";
qDebug() << jsonResponse;
qDebug() << QJsonDocument::fromJson(requestReply->readAll());
}
}
}
@ -319,6 +325,16 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
}
}
void AccountManager::persistAccountToSettings() {
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
@ -408,15 +424,10 @@ void AccountManager::requestAccessTokenFinished() {
emit loginComplete(rootURL);
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
persistAccountToSettings();
requestProfile();
generateNewKeypair();
}
} else {
// TODO: error handling
@ -434,7 +445,7 @@ void AccountManager::requestProfile() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QUrl profileURL = _authURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setPath("/api/v1/user/profile");
QNetworkRequest profileRequest(profileURL);
profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
@ -458,15 +469,8 @@ void AccountManager::requestProfileFinished() {
// the username has changed to whatever came back
emit usernameChanged(_accountInfo.getUsername());
if (_shouldPersistToSettingsFile) {
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
// store the whole profile into the local settings
persistAccountToSettings();
} else {
// TODO: error handling
@ -478,3 +482,57 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
// TODO: error handling
qDebug() << "AccountManager requestProfileError - " << error;
}
void AccountManager::generateNewKeypair() {
// setup a new QThread to generate the keypair on, in case it takes a while
QThread* generateThread = new QThread(this);
// setup a keypair generator
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator();
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
this, &AccountManager::handleKeypairGenerationError);
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
keypairGenerator->moveToThread(generateThread);
qDebug() << "Starting worker thread to generate 2048-bit RSA key-pair.";
generateThread->start();
}
void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) {
qDebug() << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key.";
// set the private key on our data-server account info
_accountInfo.setPrivateKey(privateKey);
persistAccountToSettings();
// upload the public key so data-web has an up-to-date key
const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
// setup a multipart upload to send up the public key
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart keyPart;
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
keyPart.setBody(publicKey);
requestMultiPart->append(keyPart);
authenticatedRequest(PUBLIC_KEY_UPDATE_PATH, QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QByteArray(), requestMultiPart);
// get rid of the keypair generator now that we don't need it anymore
sender()->deleteLater();
}
void AccountManager::handleKeypairGenerationError() {
// for now there isn't anything we do with this except get the worker thread to clean up
sender()->deleteLater();
}

View file

@ -70,7 +70,7 @@ public:
void requestAccessToken(const QString& login, const QString& password);
void requestProfile();
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
public slots:
void requestAccessTokenFinished();
@ -91,13 +91,19 @@ signals:
void balanceChanged(qint64 newBalance);
private slots:
void processReply();
void handleKeypairGenerationError();
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
private:
AccountManager();
AccountManager(AccountManager const& other); // not implemented
void operator=(AccountManager const& other); // not implemented
void persistAccountToSettings();
void passSuccessToCallback(QNetworkReply* reply);
void passErrorToCallback(QNetworkReply* reply);
void generateNewKeypair();
Q_INVOKABLE void invokedRequest(const QString& path,
bool requiresAuthentication,

View file

@ -10,6 +10,7 @@
//
#include <qdebug.h>
#include <qjsondocument.h>
#include <qregexp.h>
#include <qstringlist.h>
@ -134,8 +135,9 @@ void AddressManager::handleLookupString(const QString& lookupString) {
}
}
void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
QJsonObject dataObject = jsonObject["data"].toObject();
void AddressManager::handleAPIResponse(QNetworkReply& requestReply) {
QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object();
QJsonObject dataObject = responseObject["data"].toObject();
const QString ADDRESS_API_DOMAIN_KEY = "domain";
const QString ADDRESS_API_ONLINE_KEY = "online";
@ -190,7 +192,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
} else {
qDebug() << "Received an address manager API response with no domain key. Cannot parse.";
qDebug() << jsonObject;
qDebug() << responseObject;
}
} else {
// we've been told that this result exists but is offline, emit our signal so the application can handle

View file

@ -42,7 +42,7 @@ public:
public slots:
void handleLookupString(const QString& lookupString);
void handleAPIResponse(const QJsonObject& jsonObject);
void handleAPIResponse(QNetworkReply& requestReply);
void handleAPIError(QNetworkReply& errorReply);
void goToUser(const QString& username);
signals:

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <openssl/rsa.h>
#include <qjsondocument.h>
#include <QtCore/QDebug>
#include "DataServerAccountInfo.h"
@ -20,7 +23,9 @@ DataServerAccountInfo::DataServerAccountInfo() :
_discourseApiKey(),
_walletID(),
_balance(0),
_hasBalance(false)
_hasBalance(false),
_privateKey(),
_usernameSignature()
{
}
@ -33,6 +38,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
_walletID = otherInfo._walletID;
_balance = otherInfo._balance;
_hasBalance = otherInfo._hasBalance;
_privateKey = otherInfo._privateKey;
}
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
@ -51,6 +57,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
swap(_walletID, otherInfo._walletID);
swap(_balance, otherInfo._balance);
swap(_hasBalance, otherInfo._hasBalance);
swap(_privateKey, otherInfo._privateKey);
}
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
@ -61,6 +68,9 @@ void DataServerAccountInfo::setUsername(const QString& username) {
if (_username != username) {
_username = username;
// clear our username signature so it has to be re-created
_usernameSignature = QByteArray();
qDebug() << "Username changed to" << username;
}
}
@ -92,7 +102,8 @@ void DataServerAccountInfo::setBalance(qint64 balance) {
}
}
void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) {
void DataServerAccountInfo::setBalanceFromJSON(QNetworkReply& requestReply) {
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
if (jsonObject["status"].toString() == "success") {
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble();
setBalance(balanceInSatoshis);
@ -111,12 +122,55 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
setWalletID(QUuid(user["wallet_id"].toString()));
}
const QByteArray& DataServerAccountInfo::getUsernameSignature() {
if (_usernameSignature.isEmpty()) {
if (!_privateKey.isEmpty()) {
const char* privateKeyData = _privateKey.constData();
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
reinterpret_cast<const unsigned char**>(&privateKeyData),
_privateKey.size());
if (rsaPrivateKey) {
QByteArray usernameByteArray = _username.toUtf8();
_usernameSignature.resize(RSA_size(rsaPrivateKey));
int encryptReturn = RSA_private_encrypt(usernameByteArray.size(),
reinterpret_cast<const unsigned char*>(usernameByteArray.constData()),
reinterpret_cast<unsigned char*>(_usernameSignature.data()),
rsaPrivateKey, RSA_PKCS1_PADDING);
if (encryptReturn == -1) {
qDebug() << "Error encrypting username signature.";
qDebug() << "Will re-attempt on next domain-server check in.";
_usernameSignature = QByteArray();
}
// free the private key RSA struct now that we are done with it
RSA_free(rsaPrivateKey);
} else {
qDebug() << "Could not create RSA struct from QByteArray private key.";
qDebug() << "Will re-attempt on next domain-server check in.";
}
}
}
return _usernameSignature;
}
void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) {
_privateKey = privateKey;
// clear our username signature so it has to be re-created
_usernameSignature = QByteArray();
}
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey << info._walletID;
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
<< info._walletID << info._privateKey;
return out;
}
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey >> info._walletID;
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
>> info._walletID >> info._privateKey;
return in;
}

View file

@ -13,6 +13,7 @@
#define hifi_DataServerAccountInfo_h
#include <QtCore/QObject>
#include <qnetworkreply.h>
#include <QtCore/QUuid>
#include "OAuthAccessToken.h"
@ -41,13 +42,17 @@ public:
const QUuid& getWalletID() const { return _walletID; }
void setWalletID(const QUuid& walletID);
const QByteArray& getUsernameSignature();
bool hasPrivateKey() const { return !_privateKey.isEmpty(); }
void setPrivateKey(const QByteArray& privateKey);
qint64 getBalance() const { return _balance; }
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
void setBalance(qint64 balance);
bool hasBalance() const { return _hasBalance; }
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject);
Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply);
bool hasProfile() const;
@ -67,6 +72,8 @@ private:
QUuid _walletID;
qint64 _balance;
bool _hasBalance;
QByteArray _privateKey;
QByteArray _usernameSignature;
};
#endif // hifi_DataServerAccountInfo_h

View file

@ -679,7 +679,6 @@ void LimitedNodeList::updateLocalSockAddr() {
qDebug() << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr;
}
_localSockAddr = newSockAddr;
emit localSockAddrChanged(_localSockAddr);

View file

@ -101,6 +101,8 @@ public:
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
SharedNodePointer updateSocketsForNode(const QUuid& uuid,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
void processKillNode(const QByteArray& datagram);

View file

@ -302,11 +302,20 @@ void NodeList::sendDomainServerCheckIn() {
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr << _localSockAddr << (quint8) _nodeTypesOfInterest.size();
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
// copy over the bytes for node types of interest, if required
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
packetStream << nodeTypeOfInterest;
// if this is a connect request, and we can present a username signature, send it along
if (!_domainHandler.isConnected()) {
DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo();
packetStream << accountInfo.getUsername();
const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature();
if (!usernameSignature.isEmpty()) {
qDebug() << "Including username signature in domain connect request.";
packetStream << usernameSignature;
}
}
if (!isUsingDTLS) {

View file

@ -114,7 +114,7 @@ QString nameForPacketType(PacketType type) {
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainListRequest);
PACKET_TYPE_NAME_LOOKUP(PacketTypeRequestAssignment);
PACKET_TYPE_NAME_LOOKUP(PacketTypeCreateAssignment);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainOAuthRequest);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainConnectionDenied);
PACKET_TYPE_NAME_LOOKUP(PacketTypeMuteEnvironment);
PACKET_TYPE_NAME_LOOKUP(PacketTypeAudioStreamStats);
PACKET_TYPE_NAME_LOOKUP(PacketTypeDataServerConfirm);

View file

@ -38,7 +38,7 @@ enum PacketType {
PacketTypeDomainListRequest,
PacketTypeRequestAssignment,
PacketTypeCreateAssignment,
PacketTypeDomainOAuthRequest,
PacketTypeDomainConnectionDenied,
PacketTypeMuteEnvironment,
PacketTypeAudioStreamStats,
PacketTypeDataServerConfirm,
@ -81,7 +81,7 @@ typedef char PacketVersion;
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery
<< PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack

View file

@ -0,0 +1,91 @@
//
// RSAKeypairGenerator.cpp
// libraries/networking/src
//
// Created by Stephen Birarda on 2014-10-14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <qdebug.h>
#include "RSAKeypairGenerator.h"
RSAKeypairGenerator::RSAKeypairGenerator(QObject* parent) :
QObject(parent)
{
}
void RSAKeypairGenerator::generateKeypair() {
RSA* keyPair = RSA_new();
BIGNUM* exponent = BN_new();
const unsigned long RSA_KEY_EXPONENT = 65537;
BN_set_word(exponent, RSA_KEY_EXPONENT);
// seed the random number generator before we call RSA_generate_key_ex
srand(time(NULL));
const int RSA_KEY_BITS = 2048;
if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) {
qDebug() << "Error generating 2048-bit RSA Keypair -" << ERR_get_error();
emit errorGeneratingKeypair();
// we're going to bust out of here but first we cleanup the BIGNUM
BN_free(exponent);
return;
}
// we don't need the BIGNUM anymore so clean that up
BN_free(exponent);
// grab the public key and private key from the file
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER);
unsigned char* privateKeyDER = NULL;
int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
qDebug() << "Error getting DER public or private key from RSA struct -" << ERR_get_error();
emit errorGeneratingKeypair();
// cleanup the RSA struct
RSA_free(keyPair);
// cleanup the public and private key DER data, if required
if (publicKeyLength > 0) {
OPENSSL_free(publicKeyDER);
}
if (privateKeyLength > 0) {
OPENSSL_free(privateKeyDER);
}
return;
}
// we have the public key and private key in memory
// we can cleanup the RSA struct before we continue on
RSA_free(keyPair);
QByteArray publicKeyArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
QByteArray privateKeyArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
OPENSSL_free(privateKeyDER);
emit generatedKeypair(publicKeyArray, privateKeyArray);
}

View file

@ -0,0 +1,28 @@
//
// RSAKeypairGenerator.h
// libraries/networking/src
//
// Created by Stephen Birarda on 2014-10-14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RSAKeypairGenerator_h
#define hifi_RSAKeypairGenerator_h
#include <qobject.h>
class RSAKeypairGenerator : public QObject {
Q_OBJECT
public:
RSAKeypairGenerator(QObject* parent = 0);
public slots:
void generateKeypair();
signals:
void errorGeneratingKeypair();
void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
};
#endif // hifi_RSAKeypairGenerator_h

View file

@ -69,7 +69,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
multipart);
}
void UserActivityLogger::requestFinished(const QJsonObject& object) {
void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
// qDebug() << object;
}

View file

@ -39,7 +39,7 @@ public slots:
void wentTo(QString destinationType, QString destinationName);
private slots:
void requestFinished(const QJsonObject& object);
void requestFinished(QNetworkReply& requestReply);
void requestError(QNetworkReply& errorReply);
private:

View file

@ -162,12 +162,30 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
}
void OctreeRenderer::render(RenderMode renderMode) {
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0 };
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
if (_tree) {
_tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args);
_tree->unlock();
}
_meshesConsidered = args._meshesConsidered;
_meshesRendered = args._meshesRendered;
_meshesOutOfView = args._meshesOutOfView;
_meshesTooSmall = args._meshesTooSmall;
_elementsTouched = args._elementsTouched;
_itemsRendered = args._itemsRendered;
_itemsOutOfView = args._itemsOutOfView;
_itemsTooSmall = args._itemsTooSmall;
_materialSwitches = args._materialSwitches;
_trianglesRendered = args._trianglesRendered;
_quadsRendered = args._quadsRendered;
_translucentMeshPartsRendered = args._translucentMeshPartsRendered;
_opaqueMeshPartsRendered = args._opaqueMeshPartsRendered;
}
void OctreeRenderer::clear() {

View file

@ -63,10 +63,45 @@ public:
/// clears the tree
virtual void clear();
int getElementsTouched() const { return _elementsTouched; }
int getItemsRendered() const { return _itemsRendered; }
int getItemsOutOfView() const { return _itemsOutOfView; }
int getItemsTooSmall() const { return _itemsTooSmall; }
int getMeshesConsidered() const { return _meshesConsidered; }
int getMeshesRendered() const { return _meshesRendered; }
int getMeshesOutOfView() const { return _meshesOutOfView; }
int getMeshesTooSmall() const { return _meshesTooSmall; }
int getMaterialSwitches() const { return _materialSwitches; }
int getTrianglesRendered() const { return _trianglesRendered; }
int getQuadsRendered() const { return _quadsRendered; }
int getTranslucentMeshPartsRendered() const { return _translucentMeshPartsRendered; }
int getOpaqueMeshPartsRendered() const { return _opaqueMeshPartsRendered; }
protected:
Octree* _tree;
bool _managedTree;
ViewFrustum* _viewFrustum;
int _elementsTouched;
int _itemsRendered;
int _itemsOutOfView;
int _itemsTooSmall;
int _meshesConsidered;
int _meshesRendered;
int _meshesOutOfView;
int _meshesTooSmall;
int _materialSwitches;
int _trianglesRendered;
int _quadsRendered;
int _translucentMeshPartsRendered;
int _opaqueMeshPartsRendered;
};
class RenderArgs {
@ -80,6 +115,19 @@ public:
int _elementsTouched;
int _itemsRendered;
int _itemsOutOfView;
int _itemsTooSmall;
int _meshesConsidered;
int _meshesRendered;
int _meshesOutOfView;
int _meshesTooSmall;
int _materialSwitches;
int _trianglesRendered;
int _quadsRendered;
int _translucentMeshPartsRendered;
int _opaqueMeshPartsRendered;
};

View file

@ -876,3 +876,10 @@ void ViewFrustum::getFurthestPointFromCameraVoxelScale(const AACube& box, glm::v
}
}
float ViewFrustum::distanceToCamera(const glm::vec3& point) const {
glm::vec3 temp = getPosition() - point;
float distanceToPoint = sqrtf(glm::dot(temp, temp));
return distanceToPoint;
}

View file

@ -118,6 +118,8 @@ public:
// assumes box is in voxel scale, not TREE_SCALE, will scale view frustum's position accordingly
void getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const;
float distanceToCamera(const glm::vec3& point) const;
private:
// Used for keyhole calculations

View file

@ -88,13 +88,11 @@ inline bool operator==(const AABox& a, const AABox& b) {
}
inline QDebug operator<<(QDebug debug, const AABox& box) {
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
debug << "AABox[ ("
<< box.getCorner().x * (float)TREE_SCALE << "," << box.getCorner().y * (float)TREE_SCALE << "," << box.getCorner().z * (float)TREE_SCALE << " ) to ("
<< box.calcTopFarLeft().x * (float)TREE_SCALE << "," << box.calcTopFarLeft().y * (float)TREE_SCALE << "," << box.calcTopFarLeft().z * (float)TREE_SCALE << ") size: ("
<< box.getDimensions().x * (float)TREE_SCALE << "," << box.getDimensions().y * (float)TREE_SCALE << "," << box.getDimensions().z * (float)TREE_SCALE << ")"
<< " in meters]";
<< box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
<< box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
<< box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"
<< "]";
return debug;
}

View file

@ -79,12 +79,11 @@ inline bool operator==(const AACube& a, const AACube& b) {
}
inline QDebug operator<<(QDebug debug, const AACube& cube) {
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
debug << "AACube[ ("
<< cube.getCorner().x * (float)TREE_SCALE << "," << cube.getCorner().y * (float)TREE_SCALE << "," << cube.getCorner().z * (float)TREE_SCALE << " ) to ("
<< cube.calcTopFarLeft().x * (float)TREE_SCALE << "," << cube.calcTopFarLeft().y * (float)TREE_SCALE << "," << cube.calcTopFarLeft().z * (float)TREE_SCALE << ") size: ("
<< cube.getDimensions().x * (float)TREE_SCALE << "," << cube.getDimensions().y * (float)TREE_SCALE << "," << cube.getDimensions().z * (float)TREE_SCALE << ")"
<< " in meters]";
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
<< cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
<< cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"
<< "]";
return debug;
}

View file

@ -129,26 +129,31 @@ template<typename T> inline void ByteCountCoded<T>::decode(const QByteArray& fro
// next, read the leading bits to determine the correct number of bytes to decode (may not match the QByteArray)
int encodedByteCount = 0;
int leadBits = 1;
int bitAt;
for (bitAt = 0; bitAt < bitCount; bitAt++) {
if (encodedBits.at(bitAt)) {
encodedByteCount++;
leadBits++;
} else {
break;
}
}
encodedByteCount++; // always at least one byte
int expectedBitCount = encodedByteCount * BITS_IN_BYTE;
// Now, keep reading...
int valueStartsAt = bitAt + 1;
T value = 0;
T bitValue = 1;
for (bitAt = valueStartsAt; bitAt < expectedBitCount; bitAt++) {
if(encodedBits.at(bitAt)) {
value += bitValue;
if (expectedBitCount <= (encodedBits.size() - leadBits)) {
// Now, keep reading...
int valueStartsAt = bitAt + 1;
T bitValue = 1;
for (bitAt = valueStartsAt; bitAt < expectedBitCount; bitAt++) {
if(encodedBits.at(bitAt)) {
value += bitValue;
}
bitValue *= 2;
}
bitValue *= 2;
}
data = value;
}

View file

@ -15,6 +15,9 @@
#include <glm/glm.hpp>
#include <QDebug>
#include "StreamUtils.h"
class Extents {
public:
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
@ -54,4 +57,15 @@ public:
glm::vec3 maximum;
};
inline QDebug operator<<(QDebug debug, const Extents& extents) {
debug << "Extents[ ("
<< extents.minimum << " ) to ("
<< extents.maximum << ") size: ("
<< (extents.maximum - extents.minimum) << ")"
<< " ]";
return debug;
}
#endif // hifi_Extents_h

View file

@ -214,10 +214,12 @@ template<typename Enum> inline void PropertyFlags<Enum>::decode(const QByteArray
// next, read the leading bits to determine the correct number of bytes to decode (may not match the QByteArray)
int encodedByteCount = 0;
int leadBits = 1;
int bitAt;
for (bitAt = 0; bitAt < bitCount; bitAt++) {
if (encodedBits.at(bitAt)) {
encodedByteCount++;
leadBits++;
} else {
break;
}
@ -228,10 +230,12 @@ template<typename Enum> inline void PropertyFlags<Enum>::decode(const QByteArray
int expectedBitCount = encodedByteCount * BITS_PER_BYTE;
// Now, keep reading...
int flagsStartAt = bitAt + 1;
for (bitAt = flagsStartAt; bitAt < expectedBitCount; bitAt++) {
if (encodedBits.at(bitAt)) {
setHasProperty((Enum)(bitAt - flagsStartAt));
if (expectedBitCount <= (encodedBits.size() - leadBits)) {
int flagsStartAt = bitAt + 1;
for (bitAt = flagsStartAt; bitAt < expectedBitCount; bitAt++) {
if (encodedBits.at(bitAt)) {
setHasProperty((Enum)(bitAt - flagsStartAt));
}
}
}
}