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

View file

@ -74,8 +74,14 @@ private:
int _numStatFrames; int _numStatFrames;
int _sumListeners; int _sumListeners;
int _sumMixes; int _sumMixes;
QHash<QString, AABox> _audioZones; 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; static InboundAudioStream::Settings _streamSettings;

View file

@ -861,8 +861,6 @@ void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const H
} }
} else if (packetType == PacketTypeJurisdictionRequest) { } else if (packetType == PacketTypeJurisdictionRequest) {
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket); _jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
} else if (packetType == PacketTypeSignedTransactionPayment) {
handleSignedTransactionPayment(packetType, receivedPacket);
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) { } else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket); _octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
} else { } else {
@ -1245,51 +1243,6 @@ QString OctreeServer::getStatusLink() {
return result; 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() { 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 // 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 // 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 nodeKilled(SharedNodePointer node);
void sendStatsPacket(); void sendStatsPacket();
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
@ -141,7 +139,6 @@ protected:
QString getConfiguration(); QString getConfiguration();
QString getStatusLink(); QString getStatusLink();
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
void setupDatagramProcessingThread(); void setupDatagramProcessingThread();
int _argc; int _argc;

View file

@ -38,4 +38,18 @@ endif ()
# link the shared hifi libraries # link the shared hifi libraries
link_hifi_libraries(embedded-webserver networking shared) 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() link_shared_dependencies()

View file

@ -59,6 +59,20 @@
"type": "password", "type": "password",
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.", "help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
"value-hidden": true "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", "type": "table",
"label": "Attenuation Coefficients", "label": "Attenuation Coefficients",
"help": "In this table you can set custom attenuation coefficients between audio zones", "help": "In this table you can set custom attenuation coefficients between audio zones",
"numbered": false, "numbered": true,
"can_order": true,
"columns": [ "columns": [
{ {
"name": "source", "name": "source",
@ -144,7 +159,7 @@
}, },
{ {
"name": "audio_buffer", "name": "audio_buffer",
"label": "Audio Buffer", "label": "Audio Buffers",
"assignment-types": [0], "assignment-types": [0],
"settings": [ "settings": [
{ {

View file

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

View file

@ -8,7 +8,15 @@ var Settings = {
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row', ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
DEL_ROW_BUTTON_CLASS: 'del-row', DEL_ROW_BUTTON_CLASS: 'del-row',
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove 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', 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' NEW_ROW_CLASS: 'new-row'
}; };
@ -110,6 +118,14 @@ $(document).ready(function(){
$('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ $('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
deleteTableRow(this); 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){ $('#settings-form').on('keypress', 'table input', function(e){
if (e.keyCode == 13) { if (e.keyCode == 13) {
@ -120,7 +136,7 @@ $(document).ready(function(){
if (sibling.hasClass(Settings.DATA_COL_CLASS)) { if (sibling.hasClass(Settings.DATA_COL_CLASS)) {
// set focus to next input // set focus to next input
sibling.find('input').focus() 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() sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
// set focus to the first input in the new row // 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) { function makeTable(setting, setting_name, setting_value) {
var isArray = !_.has(setting, 'key') 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>" : "" var html = (setting.label) ? "<label class='control-label'>" + setting.label + "</label>" : ""
html += "<span class='help-block'>" + setting.help + "</span>" html += "<span class='help-block'>" + setting.help + "</span>"
html += "<table class='table table-bordered' data-short-name='" + setting.name + "' name='" + setting_name 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='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 // populate rows in the table from existing values
var row_num = 1 var row_num = 1
@ -279,13 +303,13 @@ function makeTable(setting, setting_name, setting_value) {
html += "<td class='" + Settings.DATA_COL_CLASS + "'>" html += "<td class='" + Settings.DATA_COL_CLASS + "'>"
if (isArray) { if (isArray) {
colIsArray = _.isArray(row) rowIsObject = setting.columns.length > 1
colValue = colIsArray ? row : row[col.name] colValue = rowIsObject ? row[col.name] : row
html += colValue html += colValue
// for arrays we add a hidden input to this td so that values can be posted appropriately // 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 + "]" html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
+ (colIsArray ? "" : "." + col.name) + "' value='" + colValue + "'/>" + (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
} else if (row.hasOwnProperty(col.name)) { } else if (row.hasOwnProperty(col.name)) {
html += row[col.name] html += row[col.name]
} }
@ -293,7 +317,13 @@ function makeTable(setting, setting_name, setting_value) {
html += "</td>" 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>" html += "</tr>"
row_num++ row_num++
@ -324,8 +354,12 @@ function makeTableInputs(setting) {
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' value=''>\ <input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' value=''>\
</td>" </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>" html += "</tr>"
return html return html
@ -418,7 +452,10 @@ function addTableRow(add_glyphicon) {
} else { } else {
$(element).html(1) $(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 // Change buttons
var span = $(element).children("span") var span = $(element).children("span")
span.removeClass(Settings.ADD_ROW_SPAN_CLASSES) 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 // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table)) 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) { 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 // 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 class="nav nav-pills nav-stacked">
</ul> </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> <button class="btn btn-success save-button">Save and restart</button>
</div> </div>
</div> </div>
@ -32,25 +32,32 @@
<script id="panels-template" type="text/template"> <script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %> <% _.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"> <div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3> <h3 class="panel-title"><%- group.label %></h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
<% _.each(split_settings[0], function(setting) { %> <% _.each(split_settings[0], function(setting) { %>
<%= getFormGroup(group.name, setting, values, false, <%= getFormGroup(group.name, setting, values, false,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %> (_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %> <% }); %>
<% _.each(split_settings[1], function(setting) { %> <% if (!_.isEmpty(split_settings[1])) { %>
<%= getFormGroup(group.name, setting, values, true, <% $("#advanced-toggle-button").show() %>
(_.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))) %>
<% }); %>
<% }%>
</div> </div>
</div> </div>
<% }); %> <% }); %>
</script> </script>
<div id="panels"></div> <div id="panels"></div>
</form> </form>

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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/QDir>
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
@ -44,8 +47,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_oauthProviderURL(), _oauthProviderURL(),
_oauthClientID(), _oauthClientID(),
_hostname(), _hostname(),
_networkReplyUUIDMap(),
_sessionAuthenticationHash(),
_webAuthenticationStateSet(), _webAuthenticationStateSet(),
_cookieSessionHash(), _cookieSessionHash(),
_settingsManager() _settingsManager()
@ -80,6 +81,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// setup automatic networking settings with data server // setup automatic networking settings with data server
setupAutomaticNetworking(); 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 const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::EntityServer
<< NodeType::MetavoxelServer; << NodeType::MetavoxelServer;
@ -517,8 +519,11 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
NodeType_t nodeType; NodeType_t nodeType;
HifiSockAddr publicSockAddr, localSockAddr; 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); QUuid packetUUID = uuidFromPacketHeader(packet);
@ -551,33 +556,20 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
} }
QString connectedUsername; QList<NodeType_t> nodeInterestList;
QString username;
if (!isAssignment && !_oauthProviderURL.isEmpty() && _settingsManager.getSettingsMap().contains(ALLOWED_ROLES_CONFIG_KEY)) { QByteArray usernameSignature;
// 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)) { packetStream >> nodeInterestList >> username >> usernameSignature;
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
if (connectedUsername.isEmpty()) { if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr)) {
// we've decided this is a user that isn't allowed in, return out // this is an agent and we've decided we won't let them connect - send them a packet to deny connection
// TODO: provide information to the user so they know why they can't connect QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied);
return;
} else { // send this oauth request datagram back to the client
// we're letting this user in, don't return and remove their UUID from the hash LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr);
_sessionAuthenticationHash.remove(packetUUID);
} return;
} 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;
}
} }
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) 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 // if we have a username from an OAuth connect request, set it on the DomainServerNodeData
nodeData->setUsername(connectedUsername); nodeData->setUsername(username);
nodeData->setSendingSockAddr(senderSockAddr); nodeData->setSendingSockAddr(senderSockAddr);
// reply back to the user with a PacketTypeDomainList // 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() { QUrl DomainServer::oauthRedirectURL() {
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort()); return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
} }
@ -653,12 +739,9 @@ QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
return authorizationURL; return authorizationURL;
} }
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, int DomainServer::parseNodeDataFromByteArray(QDataStream& packetStream, NodeType_t& nodeType,
HifiSockAddr& localSockAddr, const QByteArray& packet, HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr,
const HifiSockAddr& senderSockAddr) { const HifiSockAddr& senderSockAddr) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
packetStream >> nodeType; packetStream >> nodeType;
packetStream >> publicSockAddr >> localSockAddr; packetStream >> publicSockAddr >> localSockAddr;
@ -925,7 +1008,30 @@ void DomainServer::sendPendingTransactionsToServer() {
++i; ++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) { void DomainServer::transactionJSONCallback(const QJsonObject& data) {
@ -1095,17 +1201,24 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) { if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
NodeType_t throwawayNodeType; NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress; HifiSockAddr nodePublicAddress, nodeLocalAddress;
QDataStream packetStream(receivedPacket);
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress, parseNodeDataFromByteArray(packetStream, throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr); senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress); SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID,
nodePublicAddress, nodeLocalAddress);
// update last receive to now // update last receive to now
quint64 timeNow = usecTimestampNow(); quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow); checkInNode->setLastHeardMicrostamp(timeNow);
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes)); QList<NodeType_t> nodeInterestList;
packetStream >> nodeInterestList;
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestList.toSet());
} }
break; break;
@ -1545,13 +1658,6 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
// we've redirected the user back to our homepage // we've redirected the user back to our homepage
return true; 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"; 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) { QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
// pull the access token from the returned JSON and store it with the matching session UUID // 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 // fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL; 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)); profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
return NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL)); 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"; const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) { Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {

View file

@ -52,6 +52,7 @@ public slots:
/// Called by NodeList to inform us a node has been killed /// Called by NodeList to inform us a node has been killed
void nodeKilled(SharedNodePointer node); void nodeKilled(SharedNodePointer node);
void publicKeyJSONCallback(QNetworkReply& requestReply);
void transactionJSONCallback(const QJsonObject& data); void transactionJSONCallback(const QJsonObject& data);
void restart(); void restart();
@ -82,8 +83,17 @@ private:
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr); void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr); 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); NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
const NodeSet& nodeInterestList); const NodeSet& nodeInterestList);
@ -131,13 +141,11 @@ private:
QString _oauthClientID; QString _oauthClientID;
QString _oauthClientSecret; QString _oauthClientSecret;
QString _hostname; QString _hostname;
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
QHash<QUuid, QString> _sessionAuthenticationHash;
QSet<QUuid> _webAuthenticationStateSet; QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash; QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
HifiSockAddr _localSockAddr; QHash<QString, QByteArray> _userPublicKeys;
QHash<QUuid, NetworkPeer> _connectingICEPeers; QHash<QUuid, NetworkPeer> _connectingICEPeers;
QHash<QUuid, HifiSockAddr> _connectedICEPeers; 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 that = {};
var MINIMUM_DIMENSION = 0.001; var MINIMUM_DIMENSION = 0.001;
var GRABBER_DISTANCE_TO_SIZE_RATIO = 0.015;
var mode = "UNKNOWN"; var mode = "UNKNOWN";
var overlayNames = new Array(); var overlayNames = new Array();
@ -174,6 +176,34 @@ SelectionDisplay = (function () {
var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge);
var grabberEdgeFL = 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 baseOverlayAngles = { x: 0, y: 0, z: 0 };
var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles);
@ -2423,6 +2453,32 @@ SelectionDisplay = (function () {
return true; 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) { that.mouseReleaseEvent = function(event) {
var showHandles = false; var showHandles = false;
// hide our rotation overlays..., and show our handles // hide our rotation overlays..., and show our handles

View file

@ -31,6 +31,9 @@ Script.include("libraries/ToolTip.js");
Script.include("libraries/entityPropertyDialogBox.js"); Script.include("libraries/entityPropertyDialogBox.js");
var entityPropertyDialogBox = EntityPropertyDialogBox; var entityPropertyDialogBox = EntityPropertyDialogBox;
Script.include("libraries/entityCameraTool.js");
var entityCameraTool = new EntityCameraTool();
var windowDimensions = Controller.getViewportDimensions(); var windowDimensions = Controller.getViewportDimensions();
var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/"; var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/";
var toolHeight = 50; var toolHeight = 50;
@ -163,6 +166,9 @@ var toolBar = (function () {
Overlays.editOverlay(loadFileMenuItem, { visible: active }); Overlays.editOverlay(loadFileMenuItem, { visible: active });
} }
var RESIZE_INTERVAL = 50;
var RESIZE_TIMEOUT = 20000;
var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL;
function addModel(url) { function addModel(url) {
var position; var position;
@ -176,6 +182,27 @@ var toolBar = (function () {
modelURL: url modelURL: url
}); });
print("Model added: " + 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 { } else {
print("Can't add model: Model would be out of bounds."); print("Can't add model: Model would be out of bounds.");
} }
@ -217,6 +244,9 @@ var toolBar = (function () {
isActive = !isActive; isActive = !isActive;
if (!isActive) { if (!isActive) {
selectionDisplay.unselectAll(); selectionDisplay.unselectAll();
entityCameraTool.disable();
} else {
entityCameraTool.enable();
} }
return true; return true;
} }
@ -292,6 +322,7 @@ var toolBar = (function () {
} }
return false; return false;
}; };
@ -339,7 +370,8 @@ function mousePressEvent(event) {
mouseLastPosition = { x: event.x, y: event.y }; mouseLastPosition = { x: event.x, y: event.y };
var clickedOverlay = Overlays.getOverlayAtPoint({ 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. // Event handled; do nothing.
return; return;
} else { } else {
@ -442,8 +474,8 @@ function mouseMoveEvent(event) {
return; return;
} }
// allow the selectionDisplay to handle the event first, if it doesn't handle it, then do our own thing // 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)) { if (selectionDisplay.mouseMoveEvent(event) || entityCameraTool.mouseMoveEvent(event)) {
return; return;
} }
@ -482,6 +514,7 @@ function mouseReleaseEvent(event) {
if (entitySelected) { if (entitySelected) {
tooltip.show(false); tooltip.show(false);
} }
entityCameraTool.mouseReleaseEvent(event);
} }
Controller.mousePressEvent.connect(mousePressEvent); Controller.mousePressEvent.connect(mousePressEvent);
@ -613,6 +646,10 @@ Controller.keyReleaseEvent.connect(function (event) {
} }
if (event.text == "BACKSPACE") { if (event.text == "BACKSPACE") {
handeMenuEvent("Delete"); 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}) project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed # 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}) foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) 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 any optional and required libraries
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
# perform standard include and linking for found externals # perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
@ -178,10 +177,9 @@ endif (GVERB_FOUND)
# include headers for interface and InterfaceConfig. # include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes") include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
include_directories("${OPENSSL_INCLUDE_DIR}")
target_link_libraries( 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 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 "InterfaceVersion.h"
#include "Menu.h" #include "Menu.h"
#include "ModelUploader.h" #include "ModelUploader.h"
#include "PaymentManager.h"
#include "Util.h" #include "Util.h"
#include "devices/MIDIManager.h" #include "devices/MIDIManager.h"
#include "devices/OculusManager.h" #include "devices/OculusManager.h"
@ -90,7 +89,6 @@
#include "scripting/WindowScriptingInterface.h" #include "scripting/WindowScriptingInterface.h"
#include "ui/InfoView.h" #include "ui/InfoView.h"
#include "ui/OAuthWebViewHandler.h"
#include "ui/Snapshot.h" #include "ui/Snapshot.h"
#include "ui/Stats.h" #include "ui/Stats.h"
#include "ui/TextRenderer.h" #include "ui/TextRenderer.h"
@ -220,10 +218,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
listenPort = atoi(portStr); 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 // start the nodeThread so its event loop is running
_nodeThread->start(); _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::settingsReceived, this, &Application::domainSettingsReceived);
connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDialogDisplayedFlag); 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 // 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; const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
@ -1557,9 +1546,8 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox
FaceTracker* Application::getActiveFaceTracker() { FaceTracker* Application::getActiveFaceTracker() {
return (_dde.isActive() ? static_cast<FaceTracker*>(&_dde) : return (_dde.isActive() ? static_cast<FaceTracker*>(&_dde) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) : (_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) : (_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)));
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL))));
} }
struct SendVoxelsOperationArgs { struct SendVoxelsOperationArgs {
@ -1847,7 +1835,6 @@ void Application::init() {
// initialize our face trackers after loading the menu settings // initialize our face trackers after loading the menu settings
_faceshift.init(); _faceshift.init();
_faceplus.init();
_visage.init(); _visage.init();
Leapmotion::init(); Leapmotion::init();
@ -3391,7 +3378,6 @@ void Application::resetSensors() {
_mouseX = _glWidget->width() / 2; _mouseX = _glWidget->width() / 2;
_mouseY = _glWidget->height() / 2; _mouseY = _glWidget->height() / 2;
_faceplus.reset();
_faceshift.reset(); _faceshift.reset();
_visage.reset(); _visage.reset();
_dde.reset(); _dde.reset();
@ -3486,7 +3472,7 @@ void Application::updateLocationInServer() {
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject); 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()); JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
} }
} }
@ -3529,20 +3515,18 @@ void Application::domainChanged(const QString& domainHostname) {
// reset the voxels renderer // reset the voxels renderer
_voxels.killLocalVoxels(); _voxels.killLocalVoxels();
// reset the auth URL for OAuth web view handler
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
} }
void Application::connectedToDomain(const QString& hostname) { void Application::connectedToDomain(const QString& hostname) {
AccountManager& accountManager = AccountManager::getInstance(); 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()) { QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}";
// update our domain-server with the data-server we're logged in with
QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}"; accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation,
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), domainPutJsonString.toUtf8()); JSONCallbackParameters(), domainPutJsonString.toUtf8());
} }
} }

View file

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

View file

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

View file

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

View file

@ -11,11 +11,11 @@
#include <QtCore/QWeakPointer> #include <QtCore/QWeakPointer>
#include <AccountManager.h>
#include <PerfStat.h> #include <PerfStat.h>
#include "Application.h" #include "Application.h"
#include "Menu.h" #include "Menu.h"
#include "ui/OAuthWebViewHandler.h"
#include "DatagramProcessor.h" #include "DatagramProcessor.h"
@ -136,16 +136,12 @@ void DatagramProcessor::processDatagrams() {
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size()); application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
break; break;
} }
case PacketTypeDomainOAuthRequest: { case PacketTypeDomainConnectionDenied: {
QDataStream readStream(incomingPacket); // output to the log so the user knows they got a denied connection request
readStream.skipRawData(numBytesForPacketHeader(incomingPacket)); // 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.";
QUrl authorizationURL; qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature.";
readStream >> authorizationURL; AccountManager::getInstance().checkAndSignalForAccessToken();
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
Q_ARG(const QUrl&, authorizationURL));
break; break;
} }
case PacketTypeMuteEnvironment: { case PacketTypeMuteEnvironment: {

View file

@ -8,7 +8,7 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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" #include "Hair.h"
@ -17,13 +17,13 @@
const float HAIR_DAMPING = 0.99f; const float HAIR_DAMPING = 0.99f;
const float CONSTRAINT_RELAXATION = 10.0f; const float CONSTRAINT_RELAXATION = 10.0f;
const float HAIR_ACCELERATION_COUPLING = 0.025f; const float HAIR_ACCELERATION_COUPLING = 0.045f;
const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.01f; const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.020f;
const float HAIR_ANGULAR_ACCELERATION_COUPLING = 0.001f; const float HAIR_ANGULAR_ACCELERATION_COUPLING = 0.003f;
const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f; const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
const float HAIR_STIFFNESS = 0.005f; const float HAIR_STIFFNESS = 0.00f;
const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f); const glm::vec3 HAIR_COLOR1(0.98f, 0.76f, 0.075f);
const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f); const glm::vec3 HAIR_COLOR2(0.912f, 0.184f, 0.101f);
Hair::Hair(int strands, Hair::Hair(int strands,
int links, int links,
@ -38,7 +38,8 @@ Hair::Hair(int strands,
_acceleration(0.0f), _acceleration(0.0f),
_angularVelocity(0.0f), _angularVelocity(0.0f),
_angularAcceleration(0.0f), _angularAcceleration(0.0f),
_gravity(0.0f) _gravity(0.0f),
_loudness(0.0f)
{ {
_hairPosition = new glm::vec3[_strands * _links]; _hairPosition = new glm::vec3[_strands * _links];
_hairOriginalPosition = 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]; _hairColors = new glm::vec3[_strands * _links];
_hairIsMoveable = new int[_strands * _links]; _hairIsMoveable = new int[_strands * _links];
_hairConstraints = new int[_strands * _links * HAIR_CONSTRAINTS]; // Hair can link to two others _hairConstraints = new int[_strands * _links * HAIR_CONSTRAINTS]; // Hair can link to two others
const float FACE_WIDTH = PI / 4.0f;
glm::vec3 thisVertex; glm::vec3 thisVertex;
for (int strand = 0; strand < _strands; strand++) { for (int strand = 0; strand < _strands; strand++) {
float strandAngle = randFloat() * PI; float strandAngle = randFloat() * PI;
float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH)); float azimuth;
float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI); 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)); glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
thisStrand *= _radius; thisStrand *= _radius;
@ -77,7 +81,7 @@ Hair::Hair(int strands,
_hairOriginalPosition[vertexIndex] = _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex] = thisVertex; _hairOriginalPosition[vertexIndex] = _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex] = thisVertex;
_hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * _hairThickness, 0.f, sin(strandAngle) * _hairThickness); _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()); _hairNormals[vertexIndex] = glm::normalize(randVector());
if (randFloat() < elevation / PI_OVER_TWO) { if (randFloat() < elevation / PI_OVER_TWO) {
_hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)_links); _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) { void Hair::simulate(float deltaTime) {
deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f); 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]) * _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
(_radius - glm::length(_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 // Add gravity
_hairPosition[vertexIndex] += _gravity * deltaTime; const float SCALE_GRAVITY = 0.10f;
_hairPosition[vertexIndex] += _gravity * deltaTime * SCALE_GRAVITY;
// Add linear acceleration // Add linear acceleration
_hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime; _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; _hairLastPosition[vertexIndex] = thisPosition;
} }
} }
@ -179,11 +191,23 @@ void Hair::render() {
// //
// Before calling this function, translate/rotate to the origin of the owning object // 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); glBegin(GL_QUADS);
for (int strand = 0; strand < _strands; strand++) { for (int strand = 0; strand < _strands; strand++) {
for (int link = 0; link < _links - 1; link++) { for (int link = 0; link < _links - 1; link++) {
int vertexIndex = strand * _links + 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); glNormal3fv(&_hairNormals[vertexIndex].x);
glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x, glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y, _hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
@ -198,9 +222,11 @@ void Hair::render() {
glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x, glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
_hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y, _hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
_hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z); _hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
sparkleIndex++;
} }
} }
glEnd(); glEnd();
glPopMatrix();
} }

View file

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

View file

@ -122,7 +122,8 @@ Menu::Menu() :
_hasLoginDialogDisplayed(false), _hasLoginDialogDisplayed(false),
_snapshotsLocation(), _snapshotsLocation(),
_scriptsLocation(), _scriptsLocation(),
_walletPrivateKey() _walletPrivateKey(),
_shouldRenderTableNeedsRebuilding(true)
{ {
Application *appInstance = Application::getInstance(); Application *appInstance = Application::getInstance();
@ -407,10 +408,6 @@ Menu::Menu() :
appInstance->getFaceshift(), appInstance->getFaceshift(),
SLOT(setTCPEnabled(bool))); SLOT(setTCPEnabled(bool)));
#endif #endif
#ifdef HAVE_FACEPLUS
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Faceplus, 0, true,
appInstance->getFaceplus(), SLOT(updateEnabled()));
#endif
#ifdef HAVE_VISAGE #ifdef HAVE_VISAGE
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Visage, 0, false, addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Visage, 0, false,
appInstance->getVisage(), SLOT(updateEnabled())); appInstance->getVisage(), SLOT(updateEnabled()));
@ -426,6 +423,10 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false); addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false); addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 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"); QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels");
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
@ -1550,6 +1551,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
&& _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
_voxelSizeScale *= ADJUST_LOD_DOWN_BY; _voxelSizeScale *= ADJUST_LOD_DOWN_BY;
if (_voxelSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { if (_voxelSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_voxelSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; _voxelSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
} }
@ -1572,6 +1574,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
} }
if (changed) { if (changed) {
_shouldRenderTableNeedsRebuilding = true;
if (_lodToolsDialog) { if (_lodToolsDialog) {
_lodToolsDialog->reloadSliders(); _lodToolsDialog->reloadSliders();
} }
@ -1586,14 +1589,56 @@ void Menu::resetLODAdjust() {
void Menu::setVoxelSizeScale(float sizeScale) { void Menu::setVoxelSizeScale(float sizeScale) {
_voxelSizeScale = sizeScale; _voxelSizeScale = sizeScale;
_shouldRenderTableNeedsRebuilding = true;
bumpSettings(); bumpSettings();
} }
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) { void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
_boundaryLevelAdjust = boundaryLevelAdjust; _boundaryLevelAdjust = boundaryLevelAdjust;
_shouldRenderTableNeedsRebuilding = true;
bumpSettings(); 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() { void Menu::lodTools() {
if (!_lodToolsDialog) { if (!_lodToolsDialog) {
_lodToolsDialog = new LodToolsDialog(Application::getInstance()->getGLWidget()); _lodToolsDialog = new LodToolsDialog(Application::getInstance()->getGLWidget());

View file

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

View file

@ -364,7 +364,9 @@ void ModelUploader::send() {
_progressBar = NULL; _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") { if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") {
qDebug() << "status : success"; qDebug() << "status : success";
JSONCallbackParameters callbackParams; 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) { if (_progressDialog) {
_progressDialog->accept(); _progressDialog->accept();
} }

View file

@ -40,9 +40,9 @@ public slots:
void send(); void send();
private slots: private slots:
void checkJSON(const QJsonObject& jsonResponse); void checkJSON(QNetworkReply& requestReply);
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal); void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
void uploadSuccess(const QJsonObject& jsonResponse); void uploadSuccess(QNetworkReply& requestReply);
void uploadFailed(QNetworkReply& errorReply); void uploadFailed(QNetworkReply& errorReply);
void checkS3(); void checkS3();
void processCheck(); 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 #define WORKAROUND_BROKEN_GLUT_STROKES
// see http://www.opengl.org/resources/libraries/glut/spec3/node78.html // 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() { void renderWorldBox() {
// Show edge of world // 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); 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, void drawText(int x, int y, float scale, float radians, int mono,
char const* string, const float* color) { char const* string, const float* color) {
// //
@ -219,29 +130,6 @@ void drawText(int x, int y, float scale, float radians, int mono,
glPopMatrix(); 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) { void renderCollisionOverlay(int width, int height, float magnitude, float red, float blue, float green) {
const float MIN_VISIBLE_COLLISION = 0.01f; const float MIN_VISIBLE_COLLISION = 0.01f;
if (magnitude > MIN_VISIBLE_COLLISION) { 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) { 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(); 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) { 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(); 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 // Do some basic timing tests and report the results
void runTimingTests() { void runTimingTests() {
// How long does it take to make a call to get the time? // 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 <glm/gtc/quaternion.hpp>
#include <QSettings> #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(); float randFloat();
const glm::vec3 randVector(); const glm::vec3 randVector();
void renderWorldBox(); void renderWorldBox();
int widthText(float scale, int mono, char const* string); 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, void drawText(int x, int y, float scale, float radians, int mono,
char const* string, const float* color); 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 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 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 renderBevelCornersRect(int x, int y, int width, int height, int bevelDistance);
void runTimingTests(); void runTimingTests();

View file

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

View file

@ -218,6 +218,7 @@ void MyAvatar::simulate(float deltaTime) {
_hair.setAngularVelocity((getAngularVelocity() + getHead()->getAngularVelocity()) * getHead()->getFinalOrientationInWorldFrame()); _hair.setAngularVelocity((getAngularVelocity() + getHead()->getAngularVelocity()) * getHead()->getFinalOrientationInWorldFrame());
_hair.setAngularAcceleration(getAngularAcceleration() * getHead()->getFinalOrientationInWorldFrame()); _hair.setAngularAcceleration(getAngularAcceleration() * getHead()->getFinalOrientationInWorldFrame());
_hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame()); _hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame());
_hair.setLoudness((float)getHeadData()->getAudioLoudness());
_hair.simulate(deltaTime); _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" #include "Joystick.h"
const float MAX_AXIS = 32768.0f;
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
const float MAX_AXIS = 32768.0f;
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) : Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
_instanceId(instanceId),
_name(name),
_sdlGameController(sdlGameController), _sdlGameController(sdlGameController),
_sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)), _sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)),
_instanceId(instanceId),
_name(name),
_axes(QVector<float>(SDL_JoystickNumAxes(_sdlJoystick))), _axes(QVector<float>(SDL_JoystickNumAxes(_sdlJoystick))),
_buttons(QVector<bool>(SDL_JoystickNumButtons(_sdlJoystick))) _buttons(QVector<bool>(SDL_JoystickNumButtons(_sdlJoystick)))
{ {

View file

@ -170,7 +170,6 @@ void Visage::reset() {
void Visage::updateEnabled() { void Visage::updateEnabled() {
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) && setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) &&
!Menu::getInstance()->isOptionChecked(MenuOption::Faceplus) &&
!(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && !(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) &&
Application::getInstance()->getFaceshift()->isConnectedOrConnecting())); 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) { void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* args) {
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE; bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); 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 // TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum... // when they are outside of the view frustum...
float distance = distanceToCamera(entityBox.calcCenter(), *args->_viewFrustum); float distance = args->_viewFrustum->distanceToCamera(entityBox.calcCenter());
if (wantDebug) { if (wantDebug) {
qDebug() << "------- renderElement() ----------"; qDebug() << "------- renderElement() ----------";
@ -299,23 +269,28 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
qDebug() << " entityBox:" << entityItem->getAABox(); qDebug() << " entityBox:" << entityItem->getAABox();
qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters"; qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters";
qDebug() << " largestDimension:" << entityBox.getLargestDimension() << "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); 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; bool outOfView = args->_viewFrustum->boxInFrustum(entityBox) == ViewFrustum::OUTSIDE;
if (entityItem->getGlowLevel() > 0.0f) { if (!outOfView) {
glower = new Glower(entityItem->getGlowLevel()); bool bigEnoughToRender = Menu::getInstance()->shouldRenderMesh(entityBox.getLargestDimension(), distance);
}
entityItem->render(args); if (bigEnoughToRender) {
if (glower) { renderProxies(entityItem, args);
delete glower;
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 { } else {
args->_itemsOutOfView++; args->_itemsOutOfView++;

View file

@ -76,11 +76,7 @@ public:
void deleteReleasedModels(); void deleteReleasedModels();
private: private:
QList<Model*> _releasedModels; 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); void renderProxies(const EntityItem* entity, RenderArgs* args);
}; };
#endif // hifi_EntityTreeRenderer_h #endif // hifi_EntityTreeRenderer_h

View file

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

View file

@ -18,6 +18,7 @@
#include <CapsuleShape.h> #include <CapsuleShape.h>
#include <GeometryUtil.h> #include <GeometryUtil.h>
#include <PerfStat.h>
#include <PhysicsEntity.h> #include <PhysicsEntity.h>
#include <ShapeCollider.h> #include <ShapeCollider.h>
#include <SphereShape.h> #include <SphereShape.h>
@ -44,7 +45,9 @@ Model::Model(QObject* parent) :
_pupilDilation(0.0f), _pupilDilation(0.0f),
_url("http://invalid.com"), _url("http://invalid.com"),
_blendNumber(0), _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 // we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread()); moveToThread(Application::getInstance()->thread());
@ -269,6 +272,8 @@ void Model::reset() {
for (int i = 0; i < _jointStates.size(); i++) { for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f); _jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f);
} }
_meshGroupsKnown = false;
} }
bool Model::updateGeometry() { bool Model::updateGeometry() {
@ -318,6 +323,7 @@ bool Model::updateGeometry() {
deleteGeometry(); deleteGeometry();
_dilatedTextures.clear(); _dilatedTextures.clear();
_geometry = geometry; _geometry = geometry;
_meshGroupsKnown = false;
setJointStates(newJointStates); setJointStates(newJointStates);
needToRebuild = true; needToRebuild = true;
} else if (_jointStates.isEmpty()) { } else if (_jointStates.isEmpty()) {
@ -384,7 +390,7 @@ void Model::setJointStates(QVector<JointState> states) {
_boundingRadius = radius; _boundingRadius = radius;
} }
bool Model::render(float alpha, RenderMode mode) { bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
// render the attachments // render the attachments
foreach (Model* attachment, _attachments) { foreach (Model* attachment, _attachments) {
attachment->render(alpha, mode); attachment->render(alpha, mode);
@ -392,6 +398,23 @@ bool Model::render(float alpha, RenderMode mode) {
if (_meshStates.isEmpty()) { if (_meshStates.isEmpty()) {
return false; 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 // set up dilated textures on first render after load/simulate
const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -402,6 +425,10 @@ bool Model::render(float alpha, RenderMode mode) {
_dilatedTextures.append(dilated); _dilatedTextures.append(dilated);
} }
} }
if (!_meshGroupsKnown) {
segregateMeshGroups();
}
glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_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 || mode == NORMAL_RENDER_MODE,
mode == DEFAULT_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 // render translucent meshes afterwards
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true); 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); glDisable(GL_ALPHA_TEST);
glEnable(GL_BLEND); glEnable(GL_BLEND);
@ -445,7 +492,15 @@ bool Model::render(float alpha, RenderMode mode) {
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true); Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true);
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { 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); glDepthMask(true);
@ -468,6 +523,11 @@ bool Model::render(float alpha, RenderMode mode) {
// restore all the default material settings // restore all the default material settings
Application::getInstance()->setupWorldLight(); Application::getInstance()->setupWorldLight();
if (args) {
args->_translucentMeshPartsRendered = translucentMeshPartsRendered;
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
}
return true; return true;
} }
@ -511,6 +571,22 @@ Extents Model::getUnscaledMeshExtents() const {
return scaledExtents; 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 { bool Model::getJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) { if (index == -1 || index >= _jointStates.size()) {
return false; return false;
@ -790,6 +866,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint); || (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
if (isActive() && fullUpdate) { 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 // check for scale to fit
if (_scaleToFit && !_scaledToFit) { if (_scaleToFit && !_scaledToFit) {
scaleToFit(); 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 // we retain a reference to the base geometry so that its reference count doesn't fall to zero
_baseGeometry = _nextBaseGeometry; _baseGeometry = _nextBaseGeometry;
_geometry = _nextGeometry; _geometry = _nextGeometry;
_meshGroupsKnown = false;
_nextBaseGeometry.reset(); _nextBaseGeometry.reset();
_nextGeometry.reset(); _nextGeometry.reset();
} }
@ -1200,19 +1279,353 @@ void Model::deleteGeometry() {
_blendedBlendshapeCoefficients.clear(); _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(); updateVisibleJointStates();
const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes(); 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 // exit early if the translucency doesn't match what we're drawing
const NetworkMesh& networkMesh = networkMeshes.at(i); const NetworkMesh& networkMesh = networkMeshes.at(i);
const FBXMesh& mesh = geometry.meshes.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(); const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
int vertexCount = mesh.vertices.size(); int vertexCount = mesh.vertices.size();
@ -1221,54 +1634,40 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
continue; continue;
} }
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind(); // if we got here, then check to see if this mesh is in view
if (args) {
ProgramObject* program = &_program; bool shouldRender = true;
Locations* locations = &_locations; args->_meshesConsidered++;
ProgramObject* skinProgram = &_skinProgram;
SkinLocations* skinLocations = &_skinLocations; if (args->_viewFrustum) {
GLenum specularTextureUnit = 0; shouldRender = dontCullOutOfViewMeshParts ||
if (mode == SHADOW_RENDER_MODE) { args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
program = &_shadowProgram; if (shouldRender && cullTooSmallMeshParts) {
skinProgram = &_skinShadowProgram; float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
skinLocations = &_skinShadowLocations; shouldRender = Menu::getInstance()->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
distance);
} else if (translucent && alphaThreshold == 0.0f) { if (!shouldRender) {
program = &_translucentProgram; args->_meshesTooSmall++;
locations = &_translucentLocations; }
skinProgram = &_skinTranslucentProgram; } else {
skinLocations = &_skinTranslucentLocations; args->_meshesOutOfView++;
}
} else if (!mesh.tangents.isEmpty()) { }
if (mesh.hasSpecularTexture()) {
program = &_normalSpecularMapProgram; if (shouldRender) {
locations = &_normalSpecularMapLocations; args->_meshesRendered++;
skinProgram = &_skinNormalSpecularMapProgram; } else {
skinLocations = &_skinNormalSpecularMapLocations; continue; // skip this mesh
specularTextureUnit = GL_TEXTURE2;
} else {
program = &_normalMapProgram;
locations = &_normalMapLocations;
skinProgram = &_skinNormalMapProgram;
skinLocations = &_skinNormalMapLocations;
} }
} else if (mesh.hasSpecularTexture()) {
program = &_specularMapProgram;
locations = &_specularMapLocations;
skinProgram = &_skinSpecularMapProgram;
skinLocations = &_skinSpecularMapLocations;
specularTextureUnit = GL_TEXTURE1;
} }
const MeshState& state = _meshStates.at(i); const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
ProgramObject* activeProgram = program;
Locations* activeLocations = locations;
glPushMatrix(); glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation); Application::getInstance()->loadTranslatedViewMatrix(_translation);
const MeshState& state = _meshStates.at(i);
if (state.clusterMatrices.size() > 1) { if (state.clusterMatrices.size() > 1) {
skinProgram->bind();
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false, glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
(const float*)state.clusterMatrices.constData()); (const float*)state.clusterMatrices.constData());
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) + 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); offset + vertexCount * sizeof(glm::vec4), 4);
skinProgram->enableAttributeArray(skinLocations->clusterIndices); skinProgram->enableAttributeArray(skinLocations->clusterIndices);
skinProgram->enableAttributeArray(skinLocations->clusterWeights); skinProgram->enableAttributeArray(skinLocations->clusterWeights);
activeProgram = skinProgram;
activeLocations = skinLocations;
} else { } else {
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]); glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
program->bind();
} }
activeProgram->setUniformValue(activeLocations->alphaThreshold, alphaThreshold);
if (mesh.blendshapes.isEmpty()) { if (mesh.blendshapes.isEmpty()) {
if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) {
activeProgram->setAttributeBuffer(activeLocations->tangent, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3); 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); glBindTexture(GL_TEXTURE_2D, 0);
} else { } else {
glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity); if (dontReduceMaterialSwitches || lastMaterialID != part.materialID) {
if (!(translucent && alphaThreshold == 0.0f)) { const bool wantDebug = false;
glAlphaFunc(GL_EQUAL, diffuse.a = Application::getInstance()->getGlowEffect()->getIntensity()); if (wantDebug) {
} qDebug() << "Material Changed ---------------------------------------------";
glm::vec4 specular = glm::vec4(part.specularColor, 1.0f); qDebug() << "part INDEX:" << j;
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); qDebug() << "NEW part.materialID:" << part.materialID;
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); }
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess); 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(); Texture* diffuseMap = networkPart.diffuseTexture.data();
if (mesh.isEye && diffuseMap) { if (mesh.isEye && diffuseMap) {
diffuseMap = (_dilatedTextures[i][j] = diffuseMap = (_dilatedTextures[i][j] =
static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data(); static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data();
} }
glBindTexture(GL_TEXTURE_2D, !diffuseMap ? glBindTexture(GL_TEXTURE_2D, !diffuseMap ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID()); Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
if (!mesh.tangents.isEmpty()) { if (!mesh.tangents.isEmpty()) {
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
Texture* normalMap = networkPart.normalTexture.data(); Texture* normalMap = networkPart.normalTexture.data();
glBindTexture(GL_TEXTURE_2D, !normalMap ? glBindTexture(GL_TEXTURE_2D, !normalMap ?
Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID()); Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
} }
if (specularTextureUnit) { if (specularTextureUnit) {
glActiveTexture(specularTextureUnit); glActiveTexture(specularTextureUnit);
Texture* specularMap = networkPart.specularTexture.data(); Texture* specularMap = networkPart.specularTexture.data();
glBindTexture(GL_TEXTURE_2D, !specularMap ? glBindTexture(GL_TEXTURE_2D, !specularMap ?
Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID()); Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID());
glActiveTexture(GL_TEXTURE0); 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()) { if (!mesh.colors.isEmpty()) {
@ -1401,8 +1823,10 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
} }
glPopMatrix(); glPopMatrix();
activeProgram->release();
} }
activeProgram->release();
return meshPartsRendered;
} }
void AnimationHandle::setURL(const QUrl& url) { void AnimationHandle::setURL(const QUrl& url) {

View file

@ -16,9 +16,9 @@
#include <QObject> #include <QObject>
#include <QUrl> #include <QUrl>
#include <PhysicsEntity.h> #include <AABox.h>
#include <AnimationCache.h> #include <AnimationCache.h>
#include <PhysicsEntity.h>
#include "GeometryCache.h" #include "GeometryCache.h"
#include "InterfaceConfig.h" #include "InterfaceConfig.h"
@ -30,6 +30,8 @@ class QScriptEngine;
class AnimationHandle; class AnimationHandle;
class Shape; class Shape;
class RenderArgs;
class ViewFrustum;
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer; typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer; typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
@ -84,7 +86,7 @@ public:
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; 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. /// 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 /// \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 /// Returns the unscaled extents of the model's mesh
Extents getUnscaledMeshExtents() const; 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. /// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; } const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
@ -247,7 +252,7 @@ private:
void applyNextGeometry(); void applyNextGeometry();
void deleteGeometry(); 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); QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms(); void initJointTransforms();
@ -325,6 +330,54 @@ private:
static SkinLocations _skinTranslucentLocations; static SkinLocations _skinTranslucentLocations;
static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1); 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>) 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(); int totalServers = NodeList::getInstance()->size();
lines = _expanded ? 5 : 3; 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; horizontalOffset += 5;
int columnOneHorizontalOffset = horizontalOffset;
char serverNodes[30]; char serverNodes[30];
sprintf(serverNodes, "Servers: %d", totalServers); sprintf(serverNodes, "Servers: %d", totalServers);
@ -249,6 +271,46 @@ void Stats::display(
verticalOffset += STATS_PELS_PER_LINE; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond, color); 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; verticalOffset = 0;
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1; horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1;
@ -283,7 +345,11 @@ void Stats::display(
} }
lines = _expanded ? 4 : 3; 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; horizontalOffset += 5;
@ -319,7 +385,9 @@ void Stats::display(
lines = _expanded ? 8 : 3; 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; horizontalOffset += 5;
char avatarPosition[200]; char avatarPosition[200];
@ -391,39 +459,63 @@ void Stats::display(
VoxelSystem* voxels = Application::getInstance()->getVoxels(); VoxelSystem* voxels = Application::getInstance()->getVoxels();
lines = _expanded ? 11 : 3; lines = _expanded ? 14 : 3;
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { 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, drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset,
lines * STATS_PELS_PER_LINE + 10); lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5; 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) { if (_expanded) {
// Local Voxel Memory Usage // Local Voxel Memory Usage
voxelStats.str(""); 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; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
voxelStats.str(""); voxelStats.str("");
voxelStats << voxelStats <<
"Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB / " << " Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB / " <<
"VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB"; "VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB";
if (voxels->hasVoxelMemoryUsageGPU()) { if (voxels->hasVoxelMemoryUsageGPU()) {
voxelStats << " / GPU: " << voxels->getVoxelMemoryUsageGPU() / 1000000.f << "MB"; voxelStats << " / GPU: " << voxels->getVoxelMemoryUsageGPU() / 1000000.f << "MB";
@ -434,18 +526,11 @@ void Stats::display(
// Voxel Rendering // Voxel Rendering
voxelStats.str(""); voxelStats.str("");
voxelStats.precision(4); 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; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); 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 // iterate all the current voxel stats, and list their sending modes, and total voxel counts
std::stringstream sendingMode(""); std::stringstream sendingMode("");
sendingMode << "Octree Sending Mode: ["; sendingMode << "Octree Sending Mode: [";
@ -516,44 +601,44 @@ void Stats::display(
} }
QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); 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 // Server Octree Elements
voxelStats.str(""); if (!_expanded) {
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);
voxelStats.str(""); voxelStats.str("");
voxelStats << voxelStats << "Octree Elements Server: " << qPrintable(serversTotalString)
"Internal: " << qPrintable(serversInternalString) << " " << << " Local:" << qPrintable(localTotalString);
"Leaves: " << qPrintable(serversLeavesString) << "";
verticalOffset += STATS_PELS_PER_LINE; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); 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) { 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 localInternal = VoxelTreeElement::getInternalNodeCount();
unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount(); unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount();
QString localInternalString = locale.toString((uint)localInternal); QString localInternalString = locale.toString((uint)localInternal);
QString localLeavesString = locale.toString((uint)localLeaves); QString localLeavesString = locale.toString((uint)localLeaves);
voxelStats.str(""); voxelStats.str("");
voxelStats << voxelStats << " Local: " << qPrintable(serversTotalString) <<
"Internal: " << qPrintable(localInternalString) << " " << " Internal: " << qPrintable(localInternalString) <<
"Leaves: " << qPrintable(localLeavesString) << ""; " Leaves: " << qPrintable(localLeavesString) << "";
verticalOffset += STATS_PELS_PER_LINE; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); 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); 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)) { if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
verticalOffset += STATS_PELS_PER_LINE; // space one line... verticalOffset += STATS_PELS_PER_LINE; // space one line...

View file

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

View file

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

View file

@ -115,6 +115,8 @@ public:
FBXTexture diffuseTexture; FBXTexture diffuseTexture;
FBXTexture normalTexture; FBXTexture normalTexture;
FBXTexture specularTexture; FBXTexture specularTexture;
QString materialID;
}; };
/// A single mesh (with optional blendshapes) extracted from an FBX document. /// 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) target_link_libraries(${TARGET_NAME} ws2_32.lib)
endif () 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 # call macro to link our dependencies and bubble them up via a property on our target
link_shared_dependencies() link_shared_dependencies()

View file

@ -19,9 +19,11 @@
#include <QtCore/QUrlQuery> #include <QtCore/QUrlQuery>
#include <QtNetwork/QHttpMultiPart> #include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkRequest>
#include <qthread.h>
#include "NodeList.h" #include "NodeList.h"
#include "PacketHeaders.h" #include "PacketHeaders.h"
#include "RSAKeypairGenerator.h"
#include "AccountManager.h" #include "AccountManager.h"
@ -144,6 +146,12 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
} else { } else {
requestProfile(); 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) { void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
if (callbackParams.jsonCallbackReceiver) { if (callbackParams.jsonCallbackReceiver) {
// invoke the right method on the callback receiver // invoke the right method on the callback receiver
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod), 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 // remove the related reply-callback group from the map
_pendingCallbackMap.remove(requestReply); _pendingCallbackMap.remove(requestReply);
@ -295,7 +301,7 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
} else { } else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) { if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received JSON response from data-server that has no matching callback."; 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() { bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
@ -408,15 +424,10 @@ void AccountManager::requestAccessTokenFinished() {
emit loginComplete(rootURL); emit loginComplete(rootURL);
if (_shouldPersistToSettingsFile) { persistAccountToSettings();
// 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));
}
requestProfile(); requestProfile();
generateNewKeypair();
} }
} else { } else {
// TODO: error handling // TODO: error handling
@ -434,7 +445,7 @@ void AccountManager::requestProfile() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QUrl profileURL = _authURL; QUrl profileURL = _authURL;
profileURL.setPath("/api/v1/users/profile"); profileURL.setPath("/api/v1/user/profile");
QNetworkRequest profileRequest(profileURL); QNetworkRequest profileRequest(profileURL);
profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
@ -458,15 +469,8 @@ void AccountManager::requestProfileFinished() {
// the username has changed to whatever came back // the username has changed to whatever came back
emit usernameChanged(_accountInfo.getUsername()); emit usernameChanged(_accountInfo.getUsername());
if (_shouldPersistToSettingsFile) { // store the whole profile into the local settings
// store the whole profile into the local settings persistAccountToSettings();
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
} else { } else {
// TODO: error handling // TODO: error handling
@ -478,3 +482,57 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
// TODO: error handling // TODO: error handling
qDebug() << "AccountManager requestProfileError - " << error; 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 requestAccessToken(const QString& login, const QString& password);
void requestProfile(); void requestProfile();
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; } DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
public slots: public slots:
void requestAccessTokenFinished(); void requestAccessTokenFinished();
@ -91,13 +91,19 @@ signals:
void balanceChanged(qint64 newBalance); void balanceChanged(qint64 newBalance);
private slots: private slots:
void processReply(); void processReply();
void handleKeypairGenerationError();
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
private: private:
AccountManager(); AccountManager();
AccountManager(AccountManager const& other); // not implemented AccountManager(AccountManager const& other); // not implemented
void operator=(AccountManager const& other); // not implemented void operator=(AccountManager const& other); // not implemented
void persistAccountToSettings();
void passSuccessToCallback(QNetworkReply* reply); void passSuccessToCallback(QNetworkReply* reply);
void passErrorToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply);
void generateNewKeypair();
Q_INVOKABLE void invokedRequest(const QString& path, Q_INVOKABLE void invokedRequest(const QString& path,
bool requiresAuthentication, bool requiresAuthentication,

View file

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

View file

@ -9,6 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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 <QtCore/QDebug>
#include "DataServerAccountInfo.h" #include "DataServerAccountInfo.h"
@ -20,7 +23,9 @@ DataServerAccountInfo::DataServerAccountInfo() :
_discourseApiKey(), _discourseApiKey(),
_walletID(), _walletID(),
_balance(0), _balance(0),
_hasBalance(false) _hasBalance(false),
_privateKey(),
_usernameSignature()
{ {
} }
@ -33,6 +38,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
_walletID = otherInfo._walletID; _walletID = otherInfo._walletID;
_balance = otherInfo._balance; _balance = otherInfo._balance;
_hasBalance = otherInfo._hasBalance; _hasBalance = otherInfo._hasBalance;
_privateKey = otherInfo._privateKey;
} }
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
@ -51,6 +57,7 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
swap(_walletID, otherInfo._walletID); swap(_walletID, otherInfo._walletID);
swap(_balance, otherInfo._balance); swap(_balance, otherInfo._balance);
swap(_hasBalance, otherInfo._hasBalance); swap(_hasBalance, otherInfo._hasBalance);
swap(_privateKey, otherInfo._privateKey);
} }
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
@ -61,6 +68,9 @@ void DataServerAccountInfo::setUsername(const QString& username) {
if (_username != username) { if (_username != username) {
_username = username; _username = username;
// clear our username signature so it has to be re-created
_usernameSignature = QByteArray();
qDebug() << "Username changed to" << username; 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") { if (jsonObject["status"].toString() == "success") {
qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble(); qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble();
setBalance(balanceInSatoshis); setBalance(balanceInSatoshis);
@ -111,12 +122,55 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
setWalletID(QUuid(user["wallet_id"].toString())); 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) { 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; return out;
} }
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { 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; return in;
} }

View file

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

View file

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

View file

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

View file

@ -302,11 +302,20 @@ void NodeList::sendDomainServerCheckIn() {
QDataStream packetStream(&domainServerPacket, QIODevice::Append); QDataStream packetStream(&domainServerPacket, QIODevice::Append);
// pack our data to send to the domain-server // 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) { // if this is a connect request, and we can present a username signature, send it along
packetStream << nodeTypeOfInterest; 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) { if (!isUsingDTLS) {

View file

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

View file

@ -38,7 +38,7 @@ enum PacketType {
PacketTypeDomainListRequest, PacketTypeDomainListRequest,
PacketTypeRequestAssignment, PacketTypeRequestAssignment,
PacketTypeCreateAssignment, PacketTypeCreateAssignment,
PacketTypeDomainOAuthRequest, PacketTypeDomainConnectionDenied,
PacketTypeMuteEnvironment, PacketTypeMuteEnvironment,
PacketTypeAudioStreamStats, PacketTypeAudioStreamStats,
PacketTypeDataServerConfirm, PacketTypeDataServerConfirm,
@ -81,7 +81,7 @@ typedef char PacketVersion;
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>() const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest << PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery
<< PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack << 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); multipart);
} }
void UserActivityLogger::requestFinished(const QJsonObject& object) { void UserActivityLogger::requestFinished(QNetworkReply& requestReply) {
// qDebug() << object; // qDebug() << object;
} }

View file

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

View file

@ -162,12 +162,30 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
} }
void OctreeRenderer::render(RenderMode renderMode) { 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) { if (_tree) {
_tree->lockForRead(); _tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args); _tree->recurseTreeWithOperation(renderOperation, &args);
_tree->unlock(); _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() { void OctreeRenderer::clear() {

View file

@ -63,10 +63,45 @@ public:
/// clears the tree /// clears the tree
virtual void clear(); 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: protected:
Octree* _tree; Octree* _tree;
bool _managedTree; bool _managedTree;
ViewFrustum* _viewFrustum; 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 { class RenderArgs {
@ -80,6 +115,19 @@ public:
int _elementsTouched; int _elementsTouched;
int _itemsRendered; int _itemsRendered;
int _itemsOutOfView; 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 // 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; void getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const;
float distanceToCamera(const glm::vec3& point) const;
private: private:
// Used for keyhole calculations // 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) { 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[ (" debug << "AABox[ ("
<< box.getCorner().x * (float)TREE_SCALE << "," << box.getCorner().y * (float)TREE_SCALE << "," << box.getCorner().z * (float)TREE_SCALE << " ) to (" << box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
<< box.calcTopFarLeft().x * (float)TREE_SCALE << "," << box.calcTopFarLeft().y * (float)TREE_SCALE << "," << box.calcTopFarLeft().z * (float)TREE_SCALE << ") size: (" << box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
<< box.getDimensions().x * (float)TREE_SCALE << "," << box.getDimensions().y * (float)TREE_SCALE << "," << box.getDimensions().z * (float)TREE_SCALE << ")" << box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"
<< " in meters]"; << "]";
return debug; 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) { 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[ (" debug << "AACube[ ("
<< cube.getCorner().x * (float)TREE_SCALE << "," << cube.getCorner().y * (float)TREE_SCALE << "," << cube.getCorner().z * (float)TREE_SCALE << " ) to (" << cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
<< cube.calcTopFarLeft().x * (float)TREE_SCALE << "," << cube.calcTopFarLeft().y * (float)TREE_SCALE << "," << cube.calcTopFarLeft().z * (float)TREE_SCALE << ") size: (" << cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
<< cube.getDimensions().x * (float)TREE_SCALE << "," << cube.getDimensions().y * (float)TREE_SCALE << "," << cube.getDimensions().z * (float)TREE_SCALE << ")" << cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"
<< " in meters]"; << "]";
return debug; 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) // next, read the leading bits to determine the correct number of bytes to decode (may not match the QByteArray)
int encodedByteCount = 0; int encodedByteCount = 0;
int leadBits = 1;
int bitAt; int bitAt;
for (bitAt = 0; bitAt < bitCount; bitAt++) { for (bitAt = 0; bitAt < bitCount; bitAt++) {
if (encodedBits.at(bitAt)) { if (encodedBits.at(bitAt)) {
encodedByteCount++; encodedByteCount++;
leadBits++;
} else { } else {
break; break;
} }
} }
encodedByteCount++; // always at least one byte encodedByteCount++; // always at least one byte
int expectedBitCount = encodedByteCount * BITS_IN_BYTE; int expectedBitCount = encodedByteCount * BITS_IN_BYTE;
// Now, keep reading...
int valueStartsAt = bitAt + 1;
T value = 0; T value = 0;
T bitValue = 1;
for (bitAt = valueStartsAt; bitAt < expectedBitCount; bitAt++) { if (expectedBitCount <= (encodedBits.size() - leadBits)) {
if(encodedBits.at(bitAt)) { // Now, keep reading...
value += bitValue; 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; data = value;
} }

View file

@ -15,6 +15,9 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <QDebug>
#include "StreamUtils.h"
class Extents { class Extents {
public: public:
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively /// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
@ -54,4 +57,15 @@ public:
glm::vec3 maximum; 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 #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) // next, read the leading bits to determine the correct number of bytes to decode (may not match the QByteArray)
int encodedByteCount = 0; int encodedByteCount = 0;
int leadBits = 1;
int bitAt; int bitAt;
for (bitAt = 0; bitAt < bitCount; bitAt++) { for (bitAt = 0; bitAt < bitCount; bitAt++) {
if (encodedBits.at(bitAt)) { if (encodedBits.at(bitAt)) {
encodedByteCount++; encodedByteCount++;
leadBits++;
} else { } else {
break; break;
} }
@ -228,10 +230,12 @@ template<typename Enum> inline void PropertyFlags<Enum>::decode(const QByteArray
int expectedBitCount = encodedByteCount * BITS_PER_BYTE; int expectedBitCount = encodedByteCount * BITS_PER_BYTE;
// Now, keep reading... // Now, keep reading...
int flagsStartAt = bitAt + 1; if (expectedBitCount <= (encodedBits.size() - leadBits)) {
for (bitAt = flagsStartAt; bitAt < expectedBitCount; bitAt++) { int flagsStartAt = bitAt + 1;
if (encodedBits.at(bitAt)) { for (bitAt = flagsStartAt; bitAt < expectedBitCount; bitAt++) {
setHasProperty((Enum)(bitAt - flagsStartAt)); if (encodedBits.at(bitAt)) {
setHasProperty((Enum)(bitAt - flagsStartAt));
}
} }
} }
} }