Merge branch 'master' of https://github.com/worklist/hifi into modelserver

This commit is contained in:
ZappoMan 2014-05-05 12:36:03 -07:00
commit 3bb72c04a5
48 changed files with 1384 additions and 497 deletions

View file

@ -39,7 +39,7 @@ you to run the full stack of the virtual world.
In order to set up your own virtual world, you need to set up and run your own In order to set up your own virtual world, you need to set up and run your own
local "domain". local "domain".
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, and meta-voxels. The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models.
Follow the instructions in the [build guide](BUILD.md) to build the various components. Follow the instructions in the [build guide](BUILD.md) to build the various components.
@ -57,7 +57,7 @@ Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal win
This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option.
./assignment-client -n 5 ./assignment-client -n 6
To test things out you'll want to run the Interface client. To test things out you'll want to run the Interface client.

View file

@ -191,9 +191,10 @@ void Agent::run() {
// figure out the URL for the script for this agent assignment // figure out the URL for the script for this agent assignment
QUrl scriptURL; QUrl scriptURL;
if (_payload.isEmpty()) { if (_payload.isEmpty()) {
scriptURL = QUrl(QString("http://%1:8080/assignment/%2") scriptURL = QUrl(QString("http://%1:%2/assignment/%3")
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), .arg(NodeList::getInstance()->getDomainHandler().getIP().toString())
uuidStringWithoutCurlyBraces(_uuid))); .arg(DOMAIN_SERVER_HTTP_PORT)
.arg(uuidStringWithoutCurlyBraces(_uuid)));
} else { } else {
scriptURL = QUrl(_payload); scriptURL = QUrl(_payload);
} }

View file

@ -16,6 +16,7 @@
#include <QtCore/QProcess> #include <QtCore/QProcess>
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtCore/QUrlQuery>
#include <gnutls/dtls.h> #include <gnutls/dtls.h>
@ -31,18 +32,22 @@
#include "DomainServer.h" #include "DomainServer.h"
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
DomainServer::DomainServer(int argc, char* argv[]) : DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv), QCoreApplication(argc, argv),
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
_staticAssignmentHash(), _httpsManager(NULL),
_assignmentQueue(), _allAssignments(),
_unfulfilledAssignments(),
_isUsingDTLS(false), _isUsingDTLS(false),
_x509Credentials(NULL), _x509Credentials(NULL),
_dhParams(NULL), _dhParams(NULL),
_priorityCache(NULL), _priorityCache(NULL),
_dtlsSessions() _dtlsSessions(),
_oauthProviderURL(),
_oauthClientID(),
_hostname(),
_networkReplyUUIDMap(),
_sessionAuthenticationHash()
{ {
gnutls_global_init(); gnutls_global_init();
@ -53,22 +58,20 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
if (optionallySetupDTLS()) { if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
// we either read a certificate and private key or were not passed one, good to load assignments // we either read a certificate and private key or were not passed one, good to load assignments
// and set up the node list // and set up the node list
qDebug() << "Setting up LimitedNodeList and assignments."; qDebug() << "Setting up LimitedNodeList and assignments.";
setupNodeListAndAssignments(); setupNodeListAndAssignments();
if (_isUsingDTLS) { if (_isUsingDTLS) {
// we're using DTLS and our NodeList socket is good to go, so make the required DTLS changes
// DTLS requires that IP_DONTFRAG be set
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
LimitedNodeList* nodeList = LimitedNodeList::getInstance(); LimitedNodeList* nodeList = LimitedNodeList::getInstance();
// connect our socket to read datagrams received on the DTLS socket // connect our socket to read datagrams received on the DTLS socket
connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams); connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams);
} }
_networkAccessManager = new QNetworkAccessManager(this);
} }
} }
@ -86,44 +89,7 @@ DomainServer::~DomainServer() {
gnutls_global_deinit(); gnutls_global_deinit();
} }
bool DomainServer::optionallySetupDTLS() { bool DomainServer::optionallyReadX509KeyAndCertificate() {
if (readX509KeyAndCertificate()) {
if (_x509Credentials) {
qDebug() << "Generating Diffie-Hellman parameters.";
// generate Diffie-Hellman parameters
// When short bit length is used, it might be wise to regenerate parameters often.
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
_dhParams = new gnutls_dh_params_t;
gnutls_dh_params_init(_dhParams);
gnutls_dh_params_generate2(*_dhParams, dhBits);
qDebug() << "Successfully generated Diffie-Hellman parameters.";
// set the D-H paramters on the X509 credentials
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
// setup the key used for cookie verification
_cookieKey = new gnutls_datum_t;
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
_priorityCache = new gnutls_priority_t;
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
_isUsingDTLS = true;
qDebug() << "Initial DTLS setup complete.";
}
return true;
} else {
return false;
}
}
bool DomainServer::readX509KeyAndCertificate() {
const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_CERTIFICATE_OPTION = "cert";
const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_PRIVATE_KEY_OPTION = "key";
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
@ -157,6 +123,22 @@ bool DomainServer::readX509KeyAndCertificate() {
qDebug() << "Successfully read certificate and private key."; qDebug() << "Successfully read certificate and private key.";
// we need to also pass this certificate and private key to the HTTPS manager
// this is used for Oauth callbacks when authorizing users against a data server
QFile certFile(certPath);
certFile.open(QIODevice::ReadOnly);
QFile keyFile(keyPath);
keyFile.open(QIODevice::ReadOnly);
QSslCertificate sslCertificate(&certFile);
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
_httpsManager = new HTTPSManager(DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this);
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;
} else if (!certPath.isEmpty() || !keyPath.isEmpty()) { } else if (!certPath.isEmpty() || !keyPath.isEmpty()) {
qDebug() << "Missing certificate or private key. domain-server will now quit."; qDebug() << "Missing certificate or private key. domain-server will now quit.";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
@ -166,6 +148,67 @@ bool DomainServer::readX509KeyAndCertificate() {
return true; return true;
} }
bool DomainServer::optionallySetupOAuth() {
const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider";
const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id";
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
_oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
_oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) {
if (_oauthProviderURL.isEmpty()
|| _hostname.isEmpty()
|| _oauthClientID.isEmpty()
|| _oauthClientSecret.isEmpty()) {
qDebug() << "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit.";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
return false;
} else {
qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString();
qDebug() << "OAuth Client ID is" << _oauthClientID;
}
}
return true;
}
bool DomainServer::optionallySetupDTLS() {
if (_x509Credentials) {
qDebug() << "Generating Diffie-Hellman parameters.";
// generate Diffie-Hellman parameters
// When short bit length is used, it might be wise to regenerate parameters often.
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
_dhParams = new gnutls_dh_params_t;
gnutls_dh_params_init(_dhParams);
gnutls_dh_params_generate2(*_dhParams, dhBits);
qDebug() << "Successfully generated Diffie-Hellman parameters.";
// set the D-H paramters on the X509 credentials
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
// setup the key used for cookie verification
_cookieKey = new gnutls_datum_t;
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
_priorityCache = new gnutls_priority_t;
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
_isUsingDTLS = true;
qDebug() << "Initial DTLS setup complete.";
}
return true;
}
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
const QString CUSTOM_PORT_OPTION = "port"; const QString CUSTOM_PORT_OPTION = "port";
@ -246,7 +289,8 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) { void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) {
qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash."; qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash.";
_staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); newAssignment->setIsStatic(true);
_allAssignments.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
} }
void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) { void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) {
@ -279,7 +323,9 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr
qDebug() << "URL for script is" << assignmentURL; qDebug() << "URL for script is" << assignmentURL;
// scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies // scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); SharedAssignmentPointer sharedScriptAssignment(scriptAssignment);
_unfulfilledAssignments.enqueue(sharedScriptAssignment);
_allAssignments.insert(sharedScriptAssignment->getUUID(), sharedScriptAssignment);
} }
} }
} }
@ -340,47 +386,71 @@ 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::ModelServer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
<< NodeType::MetavoxelServer; << NodeType::MetavoxelServer;
void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
NodeType_t nodeType; NodeType_t nodeType;
HifiSockAddr publicSockAddr, localSockAddr; HifiSockAddr publicSockAddr, localSockAddr;
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr); int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
QUuid assignmentUUID = uuidFromPacketHeader(packet); QUuid packetUUID = uuidFromPacketHeader(packet);
bool isStaticAssignment = _staticAssignmentHash.contains(assignmentUUID);
SharedAssignmentPointer matchingAssignment = SharedAssignmentPointer(); // check if this connect request matches an assignment in the queue
bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
if (isStaticAssignment) { SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
// this is a static assignment, make sure the UUID sent is for an assignment we're actually trying to give out if (isFulfilledOrUnfulfilledAssignment) {
matchingAssignment = matchingQueuedAssignmentForCheckIn(assignmentUUID, nodeType); matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
if (matchingAssignment) {
// remove the matching assignment from the assignment queue so we don't take the next check in
// (if it exists)
removeMatchingAssignmentFromQueue(matchingAssignment);
}
} else {
assignmentUUID = QUuid();
} }
// make sure this was either not a static assignment or it was and we had a matching one in teh queue if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
if ((!isStaticAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) || (isStaticAssignment && matchingAssignment)) { // this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
if (_sessionAuthenticationHash.contains(packetUUID)) {
if (!_sessionAuthenticationHash.value(packetUUID)) {
// we've decided this is a user that isn't allowed in, return out
// TODO: provide information to the user so they know why they can't connect
return;
} else {
// we're letting this user in, don't return and remove their UUID from the hash
_sessionAuthenticationHash.remove(packetUUID);
}
} else {
// we don't know anything about this client
// we have an OAuth provider, ask this interface client to auth against it
QByteArray oauthRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainOAuthRequest);
QDataStream oauthRequestStream(&oauthRequestByteArray, QIODevice::Append);
QUrl authorizationURL = packetUUID.isNull() ? oauthAuthorizationURL() : oauthAuthorizationURL(packetUUID);
oauthRequestStream << authorizationURL;
// send this oauth request datagram back to the client
LimitedNodeList::getInstance()->writeUnverifiedDatagram(oauthRequestByteArray, senderSockAddr);
return;
}
}
if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|| (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) {
// this was either not a static assignment or it was and we had a matching one in the queue
// create a new session UUID for this node // create a new session UUID for this node
QUuid nodeUUID = QUuid::createUuid(); QUuid nodeUUID = QUuid::createUuid();
SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType,
publicSockAddr, localSockAddr); publicSockAddr, localSockAddr);
// when the newNode is created the linked data is also created // when the newNode is created the linked data is also created
// if this was a static assignment set the UUID, set the sendingSockAddr // if this was a static assignment set the UUID, set the sendingSockAddr
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData()); DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
nodeData->setStaticAssignmentUUID(assignmentUUID); if (isFulfilledOrUnfulfilledAssignment) {
nodeData->setAssignmentUUID(packetUUID);
}
nodeData->setSendingSockAddr(senderSockAddr); nodeData->setSendingSockAddr(senderSockAddr);
// reply back to the user with a PacketTypeDomainList // reply back to the user with a PacketTypeDomainList
@ -388,6 +458,40 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe
} }
} }
QUrl DomainServer::oauthRedirectURL() {
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
}
const QString OAUTH_CLIENT_ID_QUERY_KEY = "client_id";
const QString OAUTH_REDIRECT_URI_QUERY_KEY = "redirect_uri";
QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
// for now these are all interface clients that have a GUI
// so just send them back the full authorization URL
QUrl authorizationURL = _oauthProviderURL;
const QString OAUTH_AUTHORIZATION_PATH = "/oauth/authorize";
authorizationURL.setPath(OAUTH_AUTHORIZATION_PATH);
QUrlQuery authorizationQuery;
authorizationQuery.addQueryItem(OAUTH_CLIENT_ID_QUERY_KEY, _oauthClientID);
const QString OAUTH_RESPONSE_TYPE_QUERY_KEY = "response_type";
const QString OAUTH_REPSONSE_TYPE_QUERY_VALUE = "code";
authorizationQuery.addQueryItem(OAUTH_RESPONSE_TYPE_QUERY_KEY, OAUTH_REPSONSE_TYPE_QUERY_VALUE);
const QString OAUTH_STATE_QUERY_KEY = "state";
// create a new UUID that will be the state parameter for oauth authorization AND the new session UUID for that node
authorizationQuery.addQueryItem(OAUTH_STATE_QUERY_KEY, uuidStringWithoutCurlyBraces(stateUUID));
authorizationQuery.addQueryItem(OAUTH_REDIRECT_URI_QUERY_KEY, oauthRedirectURL().toString());
authorizationURL.setQuery(authorizationQuery);
return authorizationURL;
}
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr, const QByteArray& packet, HifiSockAddr& localSockAddr, const QByteArray& packet,
const HifiSockAddr& senderSockAddr) { const HifiSockAddr& senderSockAddr) {
@ -451,52 +555,54 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE;
// if the node has any interest types, send back those nodes as well if (nodeData->isAuthenticated()) {
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { // if this authenticated node has any interest types, send back those nodes as well
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
// reset our nodeByteArray and nodeDataStream
QByteArray nodeByteArray;
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
// don't send avatar nodes to other avatars, that will come from avatar mixer // reset our nodeByteArray and nodeDataStream
nodeDataStream << *otherNode.data(); QByteArray nodeByteArray;
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
// pack the secret that these two nodes will use to communicate with each other if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
// set that on the current Node's sessionSecretHash // don't send avatar nodes to other avatars, that will come from avatar mixer
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID); nodeDataStream << *otherNode.data();
// set it on the other Node's sessionSecretHash // pack the secret that these two nodes will use to communicate with each other
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData()) QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
// set that on the current Node's sessionSecretHash
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
->getSessionSecretHash().insert(node->getUUID(), secretUUID); ->getSessionSecretHash().insert(node->getUUID(), secretUUID);
}
nodeDataStream << secretUUID;
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
// we need to break here and start a new packet
// so send the current one
if (!dtlsSession) {
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
} else {
dtlsSession->writeDatagram(broadcastPacket);
} }
// reset the broadcastPacket structure nodeDataStream << secretUUID;
broadcastPacket.resize(numBroadcastPacketLeadBytes);
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes); if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
// we need to break here and start a new packet
// so send the current one
if (!dtlsSession) {
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
} else {
dtlsSession->writeDatagram(broadcastPacket);
}
// reset the broadcastPacket structure
broadcastPacket.resize(numBroadcastPacketLeadBytes);
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
}
// append the nodeByteArray to the current state of broadcastDataStream
broadcastPacket.append(nodeByteArray);
} }
// append the nodeByteArray to the current state of broadcastDataStream
broadcastPacket.append(nodeByteArray);
} }
} }
@ -670,9 +776,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
PacketType requestType = packetTypeForPacket(receivedPacket); PacketType requestType = packetTypeForPacket(receivedPacket);
if (requestType == PacketTypeDomainConnectRequest) { if (requestType == PacketTypeDomainConnectRequest) {
// add this node to our NodeList handleConnectRequest(receivedPacket, senderSockAddr);
// and send back session UUID right away
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
} else if (requestType == PacketTypeDomainListRequest) { } else if (requestType == PacketTypeDomainListRequest) {
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
@ -738,7 +842,8 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp()); nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp());
// if the node has pool information, add it // if the node has pool information, add it
SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID()); DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
if (matchingAssignment) { if (matchingAssignment) {
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool(); nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
} }
@ -774,9 +879,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// enumerate the NodeList to find the assigned nodes // enumerate the NodeList to find the assigned nodes
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
if (_staticAssignmentHash.value(node->getUUID())) { DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
if (!nodeData->getAssignmentUUID().isNull()) {
// add the node using the UUID as the key // add the node using the UUID as the key
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID());
assignedNodesJSON[uuidString] = jsonObjectForNode(node); assignedNodesJSON[uuidString] = jsonObjectForNode(node);
} }
} }
@ -786,7 +893,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonObject queuedAssignmentsJSON; QJsonObject queuedAssignmentsJSON;
// add the queued but unfilled assignments to the json // add the queued but unfilled assignments to the json
foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) { foreach(const SharedAssignmentPointer& assignment, _unfulfilledAssignments) {
QJsonObject queuedAssignmentJSON; QJsonObject queuedAssignmentJSON;
QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID()); QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID());
@ -903,7 +1010,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
.arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool)); .arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool));
// add the script assigment to the assignment queue // add the script assigment to the assignment queue
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
_allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment);
} }
// respond with a 200 code for successful upload // respond with a 200 code for successful upload
@ -950,6 +1059,114 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return false; return false;
} }
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
const QString URI_OAUTH = "/oauth";
if (url.path() == URI_OAUTH) {
QUrlQuery codeURLQuery(url);
const QString CODE_QUERY_KEY = "code";
QString authorizationCode = codeURLQuery.queryItemValue(CODE_QUERY_KEY);
const QString STATE_QUERY_KEY = "state";
QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY));
if (!authorizationCode.isEmpty() && !stateUUID.isNull()) {
// fire off a request with this code and state to get an access token for the user
const QString OAUTH_TOKEN_REQUEST_PATH = "/oauth/token";
QUrl tokenRequestUrl = _oauthProviderURL;
tokenRequestUrl.setPath(OAUTH_TOKEN_REQUEST_PATH);
const QString OAUTH_GRANT_TYPE_POST_STRING = "grant_type=authorization_code";
QString tokenPostBody = OAUTH_GRANT_TYPE_POST_STRING;
tokenPostBody += QString("&code=%1&redirect_uri=%2&client_id=%3&client_secret=%4")
.arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret);
QNetworkRequest tokenRequest(tokenRequestUrl);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* tokenReply = _networkAccessManager->post(tokenRequest, tokenPostBody.toLocal8Bit());
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
// insert this to our pending token replies so we can associate the returned access token with the right UUID
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
}
// respond with a 200 code indicating that login is complete
connection->respond(HTTPConnection::StatusCode200);
return true;
} else {
return false;
}
}
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) {
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll());
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
// fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL));
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
_networkReplyUUIDMap.insert(profileReply, matchingSessionUUID);
}
}
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();
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
bool shouldAllowUserToConnect = false;
foreach(const QJsonValue& roleValue, userRolesArray) {
if (allowedRolesArray.contains(roleValue)) {
// the user has a role that lets them in
// set the bool to true and break
shouldAllowUserToConnect = true;
break;
}
}
qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "-" << shouldAllowUserToConnect;
// insert this UUID and a flag that indicates if they are allowed to connect
_sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect);
}
}
}
void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) { void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
QUuid oldUUID = assignment->getUUID(); QUuid oldUUID = assignment->getUUID();
assignment->resetUUID(); assignment->resetUUID();
@ -963,13 +1180,8 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer&
} }
// add the static assignment back under the right UUID, and to the queue // add the static assignment back under the right UUID, and to the queue
_staticAssignmentHash.insert(assignment->getUUID(), assignment); _allAssignments.insert(assignment->getUUID(), assignment);
_unfulfilledAssignments.enqueue(assignment);
_assignmentQueue.enqueue(assignment);
// remove the old assignment from the _staticAssignmentHash
// this must be done last so copies are created before the assignment passed by reference is killed
_staticAssignmentHash.remove(oldUUID);
} }
void DomainServer::nodeAdded(SharedNodePointer node) { void DomainServer::nodeAdded(SharedNodePointer node) {
@ -983,10 +1195,10 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
if (nodeData) { if (nodeData) {
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue // if this node's UUID matches a static assignment we need to throw it back in the assignment queue
if (!nodeData->getStaticAssignmentUUID().isNull()) { if (!nodeData->getAssignmentUUID().isNull()) {
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(nodeData->getStaticAssignmentUUID()); SharedAssignmentPointer matchedAssignment = _allAssignments.take(nodeData->getAssignmentUUID());
if (matchedAssignment) { if (matchedAssignment && matchedAssignment->isStatic()) {
refreshStaticAssignmentAndAddToQueue(matchedAssignment); refreshStaticAssignmentAndAddToQueue(matchedAssignment);
} }
} }
@ -1010,11 +1222,11 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
} }
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
QQueue<SharedAssignmentPointer>::iterator i = _assignmentQueue.begin(); QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
while (i != _assignmentQueue.end()) { while (i != _unfulfilledAssignments.end()) {
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) { if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
return _assignmentQueue.takeAt(i - _assignmentQueue.begin()); return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
} else { } else {
++i; ++i;
} }
@ -1026,9 +1238,9 @@ SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const Q
SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) { SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) {
// this is an unassigned client talking to us directly for an assignment // this is an unassigned client talking to us directly for an assignment
// go through our queue and see if there are any assignments to give out // go through our queue and see if there are any assignments to give out
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _assignmentQueue.begin(); QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _unfulfilledAssignments.begin();
while (sharedAssignment != _assignmentQueue.end()) { while (sharedAssignment != _unfulfilledAssignments.end()) {
Assignment* assignment = sharedAssignment->data(); Assignment* assignment = sharedAssignment->data();
bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes; bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes;
bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType(); bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType();
@ -1038,16 +1250,12 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) { if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) {
// remove the assignment from the queue // remove the assignment from the queue
SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment SharedAssignmentPointer deployableAssignment = _unfulfilledAssignments.takeAt(sharedAssignment
- _assignmentQueue.begin()); - _unfulfilledAssignments.begin());
if (deployableAssignment->getType() != Assignment::AgentType // until we get a connection for this assignment
|| _staticAssignmentHash.contains(deployableAssignment->getUUID())) { // put assignment back in queue but stick it at the back so the others have a chance to go out
// this is a static assignment _unfulfilledAssignments.enqueue(deployableAssignment);
// until we get a check-in from that GUID
// put assignment back in queue but stick it at the back so the others have a chance to go out
_assignmentQueue.enqueue(deployableAssignment);
}
// stop looping, we've handed out an assignment // stop looping, we've handed out an assignment
return deployableAssignment; return deployableAssignment;
@ -1061,10 +1269,10 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
} }
void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) { void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _assignmentQueue.begin(); QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin();
while (potentialMatchingAssignment != _assignmentQueue.end()) { while (potentialMatchingAssignment != _unfulfilledAssignments.end()) {
if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) { if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
_assignmentQueue.erase(potentialMatchingAssignment); _unfulfilledAssignments.erase(potentialMatchingAssignment);
// we matched and removed an assignment, bail out // we matched and removed an assignment, bail out
break; break;
@ -1078,7 +1286,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
// if the domain-server has just restarted, // if the domain-server has just restarted,
// check if there are static assignments that we need to throw into the assignment queue // check if there are static assignments that we need to throw into the assignment queue
QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _staticAssignmentHash; QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _allAssignments;
QHash<QUuid, SharedAssignmentPointer>::iterator staticAssignment = staticHashCopy.begin(); QHash<QUuid, SharedAssignmentPointer>::iterator staticAssignment = staticHashCopy.begin();
while (staticAssignment != staticHashCopy.end()) { while (staticAssignment != staticHashCopy.end()) {
// add any of the un-matched static assignments to the queue // add any of the un-matched static assignments to the queue

View file

@ -23,20 +23,21 @@
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include <Assignment.h> #include <Assignment.h>
#include <HTTPManager.h> #include <HTTPSConnection.h>
#include <LimitedNodeList.h> #include <LimitedNodeList.h>
#include "DTLSServerSession.h" #include "DTLSServerSession.h"
typedef QSharedPointer<Assignment> SharedAssignmentPointer; typedef QSharedPointer<Assignment> SharedAssignmentPointer;
class DomainServer : public QCoreApplication, public HTTPRequestHandler { class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
Q_OBJECT Q_OBJECT
public: public:
DomainServer(int argc, char* argv[]); DomainServer(int argc, char* argv[]);
~DomainServer(); ~DomainServer();
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
void exit(int retCode = 0); void exit(int retCode = 0);
@ -52,12 +53,13 @@ private slots:
void readAvailableDTLSDatagrams(); void readAvailableDTLSDatagrams();
private: private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth();
bool optionallySetupDTLS(); bool optionallySetupDTLS();
bool readX509KeyAndCertificate(); bool optionallyReadX509KeyAndCertificate();
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr); void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr); HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes); NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
@ -76,13 +78,20 @@ private:
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
void addStaticAssignmentsToQueue(); void addStaticAssignmentsToQueue();
QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
void handleTokenRequestFinished();
void handleProfileRequestFinished();
QJsonObject jsonForSocket(const HifiSockAddr& socket); QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node); QJsonObject jsonObjectForNode(const SharedNodePointer& node);
HTTPManager _HTTPManager; HTTPManager _httpManager;
HTTPSManager* _httpsManager;
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash; QHash<QUuid, SharedAssignmentPointer> _allAssignments;
QQueue<SharedAssignmentPointer> _assignmentQueue; QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
QVariantMap _argumentVariantMap; QVariantMap _argumentVariantMap;
@ -93,6 +102,15 @@ private:
gnutls_priority_t* _priorityCache; gnutls_priority_t* _priorityCache;
QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions; QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions;
QNetworkAccessManager* _networkAccessManager;
QUrl _oauthProviderURL;
QString _oauthClientID;
QString _oauthClientSecret;
QString _hostname;
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
QHash<QUuid, bool> _sessionAuthenticationHash;
}; };
#endif // hifi_DomainServer_h #endif // hifi_DomainServer_h

View file

@ -19,9 +19,10 @@
DomainServerNodeData::DomainServerNodeData() : DomainServerNodeData::DomainServerNodeData() :
_sessionSecretHash(), _sessionSecretHash(),
_staticAssignmentUUID(), _assignmentUUID(),
_statsJSONObject(), _statsJSONObject(),
_sendingSockAddr() _sendingSockAddr(),
_isAuthenticated(true)
{ {
} }

View file

@ -27,20 +27,24 @@ public:
void parseJSONStatsPacket(const QByteArray& statsPacket); void parseJSONStatsPacket(const QByteArray& statsPacket);
void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; } const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; }
bool isAuthenticated() const { return _isAuthenticated; }
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; } QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
private: private:
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
QHash<QUuid, QUuid> _sessionSecretHash; QHash<QUuid, QUuid> _sessionSecretHash;
QUuid _staticAssignmentUUID; QUuid _assignmentUUID;
QJsonObject _statsJSONObject; QJsonObject _statsJSONObject;
HifiSockAddr _sendingSockAddr; HifiSockAddr _sendingSockAddr;
bool _isAuthenticated;
}; };
#endif // hifi_DomainServerNodeData_h #endif // hifi_DomainServerNodeData_h

342
examples/editModels.js Normal file
View file

@ -0,0 +1,342 @@
//
// editModels.js
// examples
//
// Created by Clément Brisset on 4/24/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 LASER_WIDTH = 4;
var LASER_COLOR = { red: 255, green: 0, blue: 0 };
var LASER_LENGTH_FACTOR = 1.5;
var LEFT = 0;
var RIGHT = 1;
function controller(wichSide) {
this.side = wichSide;
this.palm = 2 * wichSide;
this.tip = 2 * wichSide + 1;
this.trigger = wichSide;
this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm);
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
this.oldTipPosition = Controller.getSpatialControlPosition(this.tip);
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.oldUp = Controller.getSpatialControlNormal(this.palm);
this.up = this.oldUp;
this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition));
this.front = this.oldFront;
this.oldRight = Vec3.cross(this.front, this.up);
this.right = this.oldRight;
this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
this.rotation = this.oldRotation;
this.triggerValue = Controller.getTriggerValue(this.trigger);
this.pressed = false; // is trigger pressed
this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously)
this.grabbing = false;
this.modelID;
this.laser = Overlays.addOverlay("line3d", {
position: this.palmPosition,
end: this.tipPosition,
color: LASER_COLOR,
alpha: 1,
visible: false,
lineWidth: LASER_WIDTH
});
this.guideScale = 0.02;
this.ball = Overlays.addOverlay("sphere", {
position: this.palmPosition,
size: this.guideScale,
solid: true,
color: { red: 0, green: 255, blue: 0 },
alpha: 1,
visible: false,
});
this.leftRight = Overlays.addOverlay("line3d", {
position: this.palmPosition,
end: this.tipPosition,
color: { red: 0, green: 0, blue: 255 },
alpha: 1,
visible: false,
lineWidth: LASER_WIDTH
});
this.topDown = Overlays.addOverlay("line3d", {
position: this.palmPosition,
end: this.tipPosition,
color: { red: 0, green: 0, blue: 255 },
alpha: 1,
visible: false,
lineWidth: LASER_WIDTH
});
this.grab = function (modelID) {
if (!modelID.isKnownID) {
var identify = Models.identifyModel(modelID);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + "(grab)");
return;
}
modelID = identify;
}
print("Grabbing " + modelID.id);
this.grabbing = true;
this.modelID = modelID;
}
this.release = function () {
this.grabbing = false;
this.modelID = 0;
}
this.checkTrigger = function () {
if (this.triggerValue > 0.9) {
if (this.pressed) {
this.pressing = false;
} else {
this.pressing = true;
}
this.pressed = true;
} else {
this.pressing = false;
this.pressed = false;
}
}
this.moveLaser = function () {
var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR));
Overlays.editOverlay(this.laser, {
position: this.palmPosition,
end: endPosition,
visible: true
});
Overlays.editOverlay(this.ball, {
position: endPosition,
visible: true
});
Overlays.editOverlay(this.leftRight, {
position: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)),
end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)),
visible: true
});
Overlays.editOverlay(this.topDown, {position: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)),
end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)),
visible: true
});
}
this.checkModel = function (modelID) {
if (!modelID.isKnownID) {
var identify = Models.identifyModel(modelID);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + "(checkModel)");
return;
}
modelID = identify;
}
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = this.palmPosition;
var B = this.front;
var P = Models.getModelProperties(modelID).position;
this.x = Vec3.dot(Vec3.subtract(P, A), B);
this.y = Vec3.dot(Vec3.subtract(P, A), this.up);
this.z = Vec3.dot(Vec3.subtract(P, A), this.right);
var X = Vec3.sum(A, Vec3.multiply(B, this.x));
var d = Vec3.length(Vec3.subtract(P, X));
// Vec3.print("A: ", A);
// Vec3.print("B: ", B);
// Vec3.print("Particle pos: ", P);
// print("d: " + d + ", x: " + this.x);
if (d < Models.getModelProperties(modelID).radius && 0 < this.x && this.x < LASER_LENGTH_FACTOR) {
return true;
}
return false;
}
this.update = function () {
this.oldPalmPosition = this.palmPosition;
this.oldTipPosition = this.tipPosition;
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.oldUp = this.up;
this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm));
this.oldFront = this.front;
this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition));
this.oldRight = this.right;
this.right = Vec3.normalize(Vec3.cross(this.front, this.up));
this.oldRotation = this.rotation;
this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
this.triggerValue = Controller.getTriggerValue(this.trigger);
this.checkTrigger();
if (this.pressing) {
Vec3.print("Looking at: ", this.palmPosition);
var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR);
for (var i = 0; i < foundModels.length; i++) {
print("Model found ID (" + foundModels[i].id + ")");
if (this.checkModel(foundModels[i])) {
if (this.grab(foundModels[i])) {
return;
}
}
}
}
if (!this.pressed && this.grabbing) {
// release if trigger not pressed anymore.
this.release();
}
this.moveLaser();
}
this.cleanup = function () {
Overlays.deleteOverlay(this.laser);
Overlays.deleteOverlay(this.ball);
Overlays.deleteOverlay(this.leftRight);
Overlays.deleteOverlay(this.topDown);
}
}
var leftController = new controller(LEFT);
var rightController = new controller(RIGHT);
function moveModels() {
if (leftController.grabbing) {
if (rightController.grabbing) {
var properties = Models.getModelProperties(leftController.modelID);
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5);
var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint));
var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x));
var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x));
var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5);
var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint));
var ratio = length / oldLength;
var newPosition = Vec3.sum(middle,
Vec3.multiply(Vec3.subtract(properties.position, oldMiddle), ratio));
Vec3.print("Ratio : " + ratio + " New position: ", newPosition);
var rotation = Quat.multiply(leftController.rotation,
Quat.inverse(leftController.oldRotation));
rotation = Quat.multiply(rotation, properties.modelRotation);
Models.editModel(leftController.modelID, {
position: newPosition,
//modelRotation: rotation,
radius: properties.radius * ratio
});
return;
} else {
var newPosition = Vec3.sum(leftController.palmPosition,
Vec3.multiply(leftController.front, leftController.x));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(leftController.up, leftController.y));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(leftController.right, leftController.z));
var rotation = Quat.multiply(leftController.rotation,
Quat.inverse(leftController.oldRotation));
rotation = Quat.multiply(rotation,
Models.getModelProperties(leftController.modelID).modelRotation);
Models.editModel(leftController.modelID, {
position: newPosition,
modelRotation: rotation
});
}
}
if (rightController.grabbing) {
var newPosition = Vec3.sum(rightController.palmPosition,
Vec3.multiply(rightController.front, rightController.x));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(rightController.up, rightController.y));
newPosition = Vec3.sum(newPosition,
Vec3.multiply(rightController.right, rightController.z));
var rotation = Quat.multiply(rightController.rotation,
Quat.inverse(rightController.oldRotation));
rotation = Quat.multiply(rotation,
Models.getModelProperties(rightController.modelID).modelRotation);
Models.editModel(rightController.modelID, {
position: newPosition,
modelRotation: rotation
});
}
}
function checkController(deltaTime) {
var numberOfButtons = Controller.getNumberOfButtons();
var numberOfTriggers = Controller.getNumberOfTriggers();
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
// this is expected for hydras
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
//print("no hydra connected?");
return; // bail if no hydra
}
leftController.update();
rightController.update();
moveModels();
}
function scriptEnding() {
leftController.cleanup();
rightController.cleanup();
}
Script.scriptEnding.connect(scriptEnding);
// register the call back so it fires before each data send
Script.update.connect(checkController);

View file

@ -81,9 +81,10 @@
#include "scripting/LocationScriptingInterface.h" #include "scripting/LocationScriptingInterface.h"
#include "ui/InfoView.h" #include "ui/InfoView.h"
#include "ui/OAuthWebViewHandler.h"
#include "ui/Snapshot.h" #include "ui/Snapshot.h"
#include "ui/TextRenderer.h"
#include "ui/Stats.h" #include "ui/Stats.h"
#include "ui/TextRenderer.h"
using namespace std; using namespace std;
@ -361,12 +362,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
QMutexLocker locker(&_settingsMutex); QMutexLocker locker(&_settingsMutex);
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString(); _previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
} }
connect(_window, &MainWindow::windowGeometryChanged, connect(_window, &MainWindow::windowGeometryChanged,
_runningScriptsWidget, &RunningScriptsWidget::setBoundary); _runningScriptsWidget, &RunningScriptsWidget::setBoundary);
//When -url in command line, teleport to location //When -url in command line, teleport to location
urlGoTo(argc, constArgv); urlGoTo(argc, constArgv);
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
OAuthWebViewHandler::getInstance();
// make sure the High Fidelity root CA is in our list of trusted certs
OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig();
} }
Application::~Application() { Application::~Application() {
@ -555,41 +561,6 @@ void Application::paintGL() {
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT;
const float BASE_PUSHBACK_RADIUS = 0.25f;
float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS;
glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius);
// push camera out of any intersecting avatars
foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarData.data());
if (avatar->isMyAvatar()) {
continue;
}
if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) >
avatar->getBoundingRadius() + pushbackRadius) {
continue;
}
float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal);
if (angle > PI_OVER_TWO) {
continue;
}
float scale = 1.0f - angle / PI_OVER_TWO;
scale = qMin(1.0f, scale * 2.5f);
static CollisionList collisions(64);
collisions.clear();
if (!avatar->findPlaneCollisions(plane, collisions)) {
continue;
}
for (int i = 0; i < collisions.size(); i++) {
pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale);
}
}
const float MAX_PUSHBACK = 0.35f;
pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale());
const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f;
pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale();
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
@ -3124,9 +3095,9 @@ void Application::updateWindowTitle(){
QString buildVersion = " (build " + applicationVersion() + ")"; QString buildVersion = " (build " + applicationVersion() + ")";
NodeList* nodeList = NodeList::getInstance(); NodeList* nodeList = NodeList::getInstance();
QString username = AccountManager::getInstance().getUsername(); QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
+ " @ " + nodeList->getDomainHandler().getHostname() + buildVersion; + nodeList->getDomainHandler().getHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str()); qDebug("Application title set to: %s", title.toStdString().c_str());
_window->setWindowTitle(title); _window->setWindowTitle(title);
} }
@ -3150,6 +3121,9 @@ 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) {

View file

@ -15,6 +15,7 @@
#include "Application.h" #include "Application.h"
#include "Menu.h" #include "Menu.h"
#include "ui/OAuthWebViewHandler.h"
#include "DatagramProcessor.h" #include "DatagramProcessor.h"
@ -56,13 +57,11 @@ void DatagramProcessor::processDatagrams() {
Particle::handleAddParticleResponse(incomingPacket); Particle::handleAddParticleResponse(incomingPacket);
application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket); application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
break; break;
case PacketTypeModelAddResponse: case PacketTypeModelAddResponse:
// this will keep creatorTokenIDs to IDs mapped correctly // this will keep creatorTokenIDs to IDs mapped correctly
ModelItem::handleAddModelResponse(incomingPacket); ModelItem::handleAddModelResponse(incomingPacket);
application->getModels()->getTree()->handleAddModelResponse(incomingPacket); application->getModels()->getTree()->handleAddModelResponse(incomingPacket);
break; break;
case PacketTypeParticleData: case PacketTypeParticleData:
case PacketTypeParticleErase: case PacketTypeParticleErase:
case PacketTypeModelData: case PacketTypeModelData:
@ -120,6 +119,18 @@ void DatagramProcessor::processDatagrams() {
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size()); application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
break; break;
} }
case PacketTypeDomainOAuthRequest: {
QDataStream readStream(incomingPacket);
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
QUrl authorizationURL;
readStream >> authorizationURL;
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
Q_ARG(const QUrl&, authorizationURL));
break;
}
default: default:
nodeList->processNodeData(senderSockAddr, incomingPacket); nodeList->processNodeData(senderSockAddr, incomingPacket);
break; break;

View file

@ -336,7 +336,6 @@ Menu::Menu() :
SLOT(setFilter(bool))); SLOT(setFilter(bool)));
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true);
@ -1100,7 +1099,7 @@ void Menu::toggleLoginMenuItem() {
if (accountManager.isLoggedIn()) { if (accountManager.isLoggedIn()) {
// change the menu item to logout // change the menu item to logout
_loginAction->setText("Logout " + accountManager.getUsername()); _loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername());
connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout); connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
} else { } else {
// change the menu item to login // change the menu item to login

View file

@ -340,7 +340,6 @@ namespace MenuOption {
const QString Particles = "Particles"; const QString Particles = "Particles";
const QString PasteToVoxel = "Paste to Voxel..."; const QString PasteToVoxel = "Paste to Voxel...";
const QString PipelineWarnings = "Show Render Pipeline Warnings"; const QString PipelineWarnings = "Show Render Pipeline Warnings";
const QString PlaySlaps = "Play Slaps";
const QString Preferences = "Preferences..."; const QString Preferences = "Preferences...";
const QString Quit = "Quit"; const QString Quit = "Quit";
const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadAllScripts = "Reload All Scripts";

View file

@ -153,14 +153,14 @@ bool ModelUploader::zip() {
// mixamo/autodesk defaults // mixamo/autodesk defaults
if (!mapping.contains(SCALE_FIELD)) { if (!mapping.contains(SCALE_FIELD)) {
mapping.insert(SCALE_FIELD, 10.0); mapping.insert(SCALE_FIELD, geometry.author == "www.makehuman.org" ? 150.0 : 15.0);
} }
QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointEyeLeft")) { if (!joints.contains("jointEyeLeft")) {
joints.insert("jointEyeLeft", "LeftEye"); joints.insert("jointEyeLeft", geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye");
} }
if (!joints.contains("jointEyeRight")) { if (!joints.contains("jointEyeRight")) {
joints.insert("jointEyeRight", "RightEye"); joints.insert("jointEyeRight", geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye");
} }
if (!joints.contains("jointNeck")) { if (!joints.contains("jointNeck")) {
joints.insert("jointNeck", "Neck"); joints.insert("jointNeck", "Neck");
@ -172,7 +172,8 @@ bool ModelUploader::zip() {
joints.insert("jointLean", "Spine"); joints.insert("jointLean", "Spine");
} }
if (!joints.contains("jointHead")) { if (!joints.contains("jointHead")) {
joints.insert("jointHead", geometry.applicationName == "mixamo.com" ? "HeadTop_End" : "HeadEnd"); const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";
joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");
} }
if (!joints.contains("jointLeftHand")) { if (!joints.contains("jointLeftHand")) {
joints.insert("jointLeftHand", "LeftHand"); joints.insert("jointLeftHand", "LeftHand");
@ -600,7 +601,7 @@ static void setJointText(QComboBox* box, const QString& text) {
void ModelPropertiesDialog::reset() { void ModelPropertiesDialog::reset() {
_name->setText(_originalMapping.value(NAME_FIELD).toString()); _name->setText(_originalMapping.value(NAME_FIELD).toString());
_textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString());
_scale->setValue(_originalMapping.value(SCALE_FIELD, 1.0).toDouble()); _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble());
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString()); setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());

View file

@ -34,12 +34,13 @@ XmppClient& XmppClient::getInstance() {
void XmppClient::xmppConnected() { void XmppClient::xmppConnected() {
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM); _publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
_publicChatRoom->setNickName(AccountManager::getInstance().getUsername()); _publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
_publicChatRoom->join(); _publicChatRoom->join();
} }
void XmppClient::xmppError(QXmppClient::Error error) { void XmppClient::xmppError(QXmppClient::Error error) {
qDebug() << "Error connnecting to XMPP for user " << AccountManager::getInstance().getUsername() << ": " << error; qDebug() << "Error connnecting to XMPP for user "
<< AccountManager::getInstance().getAccountInfo().getUsername() << ": " << error;
} }
void XmppClient::connectToServer() { void XmppClient::connectToServer() {
@ -50,8 +51,8 @@ void XmppClient::connectToServer() {
connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error))); connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error)));
} }
AccountManager& accountManager = AccountManager::getInstance(); AccountManager& accountManager = AccountManager::getInstance();
QString user = accountManager.getUsername(); QString user = accountManager.getAccountInfo().getUsername();
const QString& password = accountManager.getXMPPPassword(); const QString& password = accountManager.getAccountInfo().getXMPPPassword();
_xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password); _xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password);
} }

View file

@ -776,7 +776,16 @@ float Avatar::getSkeletonHeight() const {
float Avatar::getHeadHeight() const { float Avatar::getHeadHeight() const {
Extents extents = getHead()->getFaceModel().getBindExtents(); Extents extents = getHead()->getFaceModel().getBindExtents();
return extents.maximum.y - extents.minimum.y; if (!extents.isEmpty()) {
return extents.maximum.y - extents.minimum.y;
}
glm::vec3 neckPosition;
glm::vec3 headPosition;
if (_skeletonModel.getNeckPosition(neckPosition) && _skeletonModel.getHeadPosition(headPosition)) {
return glm::distance(neckPosition, headPosition);
}
const float DEFAULT_HEAD_HEIGHT = 0.1f;
return DEFAULT_HEAD_HEIGHT;
} }
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const { bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {

View file

@ -29,11 +29,11 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
neckPosition = owningAvatar->getPosition(); neckPosition = owningAvatar->getPosition();
} }
setTranslation(neckPosition); setTranslation(neckPosition);
glm::quat neckRotation; glm::quat neckParentRotation;
if (!owningAvatar->getSkeletonModel().getNeckRotation(neckRotation)) { if (!owningAvatar->getSkeletonModel().getNeckParentRotation(neckParentRotation)) {
neckRotation = owningAvatar->getOrientation(); neckParentRotation = owningAvatar->getOrientation();
} }
setRotation(neckRotation); setRotation(neckParentRotation);
const float MODEL_SCALE = 0.0006f; const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE); setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);

View file

@ -26,8 +26,6 @@ public:
virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void simulate(float deltaTime, bool fullUpdate = true);
protected:
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);

View file

@ -55,76 +55,10 @@ void Hand::simulate(float deltaTime, bool isMine) {
} }
} }
void Hand::playSlaps(PalmData& palm, Avatar* avatar) {
// Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition();
float palmCollisionDistance = 0.1f;
bool wasColliding = palm.getIsCollidingWithPalm();
palm.setIsCollidingWithPalm(false);
// If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
for (size_t j = 0; j < avatar->getHand()->getNumPalms(); j++) {
PalmData& otherPalm = avatar->getHand()->getPalms()[j];
if (!otherPalm.isActive()) {
continue;
}
glm::vec3 otherPalmPosition = otherPalm.getPosition();
if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
palm.setIsCollidingWithPalm(true);
if (!wasColliding) {
const float PALM_COLLIDE_VOLUME = 1.f;
const float PALM_COLLIDE_FREQUENCY = 1000.f;
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
PALM_COLLIDE_FREQUENCY,
PALM_COLLIDE_DURATION_MAX,
PALM_COLLIDE_DECAY_PER_SAMPLE);
// If the other person's palm is in motion, move mine downward to show I was hit
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
// add slapback here
}
}
}
}
}
// We create a static CollisionList that is recycled for each collision test. // We create a static CollisionList that is recycled for each collision test.
const float MAX_COLLISIONS_PER_AVATAR = 32; const float MAX_COLLISIONS_PER_AVATAR = 32;
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR); static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
void Hand::collideAgainstAvatarOld(Avatar* avatar, bool isMyHand) {
if (!avatar || avatar == _owningAvatar) {
// don't collide with our own hands (that is done elsewhere)
return;
}
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
for (size_t i = 0; i < getNumPalms(); i++) {
PalmData& palm = getPalms()[i];
if (!palm.isActive()) {
continue;
}
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
playSlaps(palm, avatar);
}
glm::vec3 totalPenetration;
handCollisions.clear();
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
if (isMyHand) {
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
}
}
}
if (isMyHand) {
// resolve penetration
palm.addToPosition(-totalPenetration);
}
}
}
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
if (!avatar || avatar == _owningAvatar) { if (!avatar || avatar == _owningAvatar) {
// don't collide hands against ourself (that is done elsewhere) // don't collide hands against ourself (that is done elsewhere)

View file

@ -58,7 +58,6 @@ public:
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;} const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;} const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
void collideAgainstAvatarOld(Avatar* avatar, bool isMyHand);
void collideAgainstAvatar(Avatar* avatar, bool isMyHand); void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
void collideAgainstOurself(); void collideAgainstOurself();
@ -80,8 +79,6 @@ private:
void renderLeapFingerTrails(); void renderLeapFingerTrails();
void calculateGeometry(); void calculateGeometry();
void playSlaps(PalmData& palm, Avatar* avatar);
}; };
#endif // hifi_Hand_h #endif // hifi_Hand_h

View file

@ -161,7 +161,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_leftEyePosition = _rightEyePosition = getPosition(); _leftEyePosition = _rightEyePosition = getPosition();
if (!billboard) { if (!billboard) {
_faceModel.simulate(deltaTime); _faceModel.simulate(deltaTime);
_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition); if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) {
static_cast<Avatar*>(_owningAvatar)->getSkeletonModel().getEyePositions(_leftEyePosition, _rightEyePosition);
}
} }
_eyePosition = calculateAverageEyePosition(); _eyePosition = calculateAverageEyePosition();
} }

View file

@ -221,6 +221,14 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const
glm::normalize(inverse * axes[0])) * joint.rotation; glm::normalize(inverse * axes[0])) * joint.rotation;
} }
void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
_owningAvatar->getHead()->getFaceModel().maybeUpdateNeckRotation(parentState, joint, state);
}
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state);
}
void SkeletonModel::renderJointConstraints(int jointIndex) { void SkeletonModel::renderJointConstraints(int jointIndex) {
if (jointIndex == -1) { if (jointIndex == -1) {
return; return;

View file

@ -46,6 +46,8 @@ protected:
virtual void updateJointState(int index); virtual void updateJointState(int index);
virtual void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
private: private:

View file

@ -434,6 +434,17 @@ bool Model::getNeckRotation(glm::quat& neckRotation) const {
return isActive() && getJointRotation(_geometry->getFBXGeometry().neckJointIndex, neckRotation); return isActive() && getJointRotation(_geometry->getFBXGeometry().neckJointIndex, neckRotation);
} }
bool Model::getNeckParentRotation(glm::quat& neckParentRotation) const {
if (!isActive()) {
return false;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.neckJointIndex == -1) {
return false;
}
return getJointRotation(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation);
}
bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
if (!isActive()) { if (!isActive()) {
return false; return false;

View file

@ -132,6 +132,10 @@ public:
/// \return whether or not the neck was found /// \return whether or not the neck was found
bool getNeckRotation(glm::quat& neckRotation) const; bool getNeckRotation(glm::quat& neckRotation) const;
/// Returns the rotation of the neck joint's parent.
/// \return whether or not the neck was found
bool getNeckParentRotation(glm::quat& neckRotation) const;
/// Retrieve the positions of up to two eye meshes. /// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found /// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;

View file

@ -254,7 +254,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
} }
// Update background if this is a message from the current user // Update background if this is a message from the current user
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername(); bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getAccountInfo().getUsername();
// Create message area // Create message area
ChatMessageArea* messageArea = new ChatMessageArea(true); ChatMessageArea* messageArea = new ChatMessageArea(true);

View file

@ -0,0 +1,116 @@
//
// 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 <QtWebKitWidgets/QWebView>
#include "Application.h"
#include "OAuthWebViewHandler.h"
OAuthWebViewHandler& OAuthWebViewHandler::getInstance() {
static OAuthWebViewHandler sharedInstance;
return sharedInstance;
}
OAuthWebViewHandler::OAuthWebViewHandler() :
_activeWebView(NULL),
_webViewRedisplayTimer(),
_lastAuthorizationURL()
{
}
void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() {
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
// add the High Fidelity root CA to the list of trusted CA certificates
QByteArray highFidelityCACertificate(reinterpret_cast<char*>(DTLSSession::highFidelityCADatum()->data),
DTLSSession::highFidelityCADatum()->size);
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::WindowStaysOnTopHint);
_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);
}
_activeWebView->load(codedAuthorizationURL);
_activeWebView->show();
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors,
this, &OAuthWebViewHandler::handleSSLErrors);
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();
}
}
void OAuthWebViewHandler::handleWebViewDestroyed(QObject* destroyedObject) {
_webViewRedisplayTimer.restart();
}

View file

@ -0,0 +1,41 @@
//
// 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 handleWebViewDestroyed(QObject* destroyedObject);
private:
QPointer<QWebView> _activeWebView;
QElapsedTimer _webViewRedisplayTimer;
QUrl _lastAuthorizationURL;
};
#endif // hifi_OAuthWebviewHandler_h

View file

@ -86,7 +86,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
// replace decimal . with '-' // replace decimal . with '-'
formattedLocation.replace('.', '-'); formattedLocation.replace('.', '-');
QString username = AccountManager::getInstance().getUsername(); QString username = AccountManager::getInstance().getAccountInfo().getUsername();
// normalize username, replace all non alphanumeric with '-' // normalize username, replace all non alphanumeric with '-'
username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); username.replace(QRegExp("[^A-Za-z0-9_]"), "-");

View file

@ -33,9 +33,9 @@ void Sphere3DOverlay::render() {
glDisable(GL_LIGHTING); glDisable(GL_LIGHTING);
glPushMatrix(); glPushMatrix();
glTranslatef(_position.x + _size * 0.5f, glTranslatef(_position.x,
_position.y + _size * 0.5f, _position.y,
_position.z + _size * 0.5f); _position.z);
glLineWidth(_lineWidth); glLineWidth(_lineWidth);
const int slices = 15; const int slices = 15;
if (_isSolid) { if (_isSolid) {

View file

@ -28,8 +28,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
_parentManager(parentManager), _parentManager(parentManager),
_socket(socket), _socket(socket),
_stream(socket), _stream(socket),
_address(socket->peerAddress()) { _address(socket->peerAddress())
{
// take over ownership of the socket // take over ownership of the socket
_socket->setParent(this); _socket->setParent(this);
@ -42,7 +42,7 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
HTTPConnection::~HTTPConnection() { HTTPConnection::~HTTPConnection() {
// log the destruction // log the destruction
if (_socket->error() != QAbstractSocket::UnknownSocketError) { if (_socket->error() != QAbstractSocket::UnknownSocketError) {
qDebug() << _socket->errorString(); qDebug() << _socket->errorString() << "-" << _socket->error();
} }
} }

View file

@ -18,10 +18,10 @@
#include <QDataStream> #include <QDataStream>
#include <QHash> #include <QHash>
#include <QHostAddress> #include <QtNetwork/QHostAddress>
#include <QIODevice> #include <QIODevice>
#include <QList> #include <QList>
#include <QNetworkAccessManager> #include <QtNetwork/QNetworkAccessManager>
#include <QObject> #include <QObject>
#include <QPair> #include <QPair>
#include <QUrl> #include <QUrl>

View file

@ -18,116 +18,6 @@
#include "HTTPConnection.h" #include "HTTPConnection.h"
#include "HTTPManager.h" #include "HTTPManager.h"
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) {
if (_requestHandler && _requestHandler->handleHTTPRequest(connection, url)) {
// this request was handled by our _requestHandler object
// so we don't need to attempt to do so in the document root
return true;
}
// check to see if there is a file to serve from the document root for this path
QString subPath = url.path();
// remove any slash at the beginning of the path
if (subPath.startsWith('/')) {
subPath.remove(0, 1);
}
QString filePath;
if (QFileInfo(_documentRoot + subPath).isFile()) {
filePath = _documentRoot + subPath;
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
// this could be a directory with a trailing slash
// send a redirect to the path with a slash so we can
QString redirectLocation = '/' + subPath + '/';
if (!url.query().isEmpty()) {
redirectLocation += "?" + url.query();
}
QHash<QByteArray, QByteArray> redirectHeader;
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
}
// if the last thing is a trailing slash then we want to look for index file
if (subPath.endsWith('/') || subPath.size() == 0) {
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
filePath = _documentRoot + subPath + possibleIndexFilename;
break;
}
}
}
if (!filePath.isEmpty()) {
// file exists, serve it
static QMimeDatabase mimeDatabase;
QFile localFile(filePath);
localFile.open(QIODevice::ReadOnly);
QByteArray localFileData = localFile.readAll();
QFileInfo localFileInfo(filePath);
if (localFileInfo.completeSuffix() == "shtml") {
// this is a file that may have some SSI statements
// the only thing we support is the include directive, but check the contents for that
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
QRegExp includeRegExp(includeRegExpString);
int matchPosition = 0;
QString localFileString(localFileData);
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
// check if this is a file or vitual include
bool isFileInclude = includeRegExp.cap(1) == "file";
// setup the correct file path for the included file
QString includeFilePath = isFileInclude
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
: _documentRoot + includeRegExp.cap(2);
QString replacementString;
if (QFileInfo(includeFilePath).isFile()) {
QFile includedFile(includeFilePath);
includedFile.open(QIODevice::ReadOnly);
replacementString = QString(includedFile.readAll());
} else {
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
}
// replace the match with the contents of the file, or an empty string if the file was not found
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
// push the match position forward so we can check the next match
matchPosition += includeRegExp.matchedLength();
}
localFileData = localFileString.toLocal8Bit();
}
connection->respond(HTTPConnection::StatusCode200, localFileData,
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
} else {
// respond with a 404
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
}
return true;
}
HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
QTcpServer(parent), QTcpServer(parent),
_documentRoot(documentRoot), _documentRoot(documentRoot),
@ -138,14 +28,131 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH
qDebug() << "Failed to open HTTP server socket:" << errorString(); qDebug() << "Failed to open HTTP server socket:" << errorString();
return; return;
} }
// connect the connection signal
connect(this, SIGNAL(newConnection()), SLOT(acceptConnections()));
} }
void HTTPManager::acceptConnections() { void HTTPManager::incomingConnection(qintptr socketDescriptor) {
QTcpSocket* socket; QTcpSocket* socket = new QTcpSocket(this);
while ((socket = nextPendingConnection()) != 0) {
if (socket->setSocketDescriptor(socketDescriptor)) {
new HTTPConnection(socket, this); new HTTPConnection(socket, this);
} else {
delete socket;
} }
} }
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) {
if (requestHandledByRequestHandler(connection, url)) {
// this request was handled by our request handler object
// so we don't need to attempt to do so in the document root
return true;
}
if (!_documentRoot.isEmpty()) {
// check to see if there is a file to serve from the document root for this path
QString subPath = url.path();
// remove any slash at the beginning of the path
if (subPath.startsWith('/')) {
subPath.remove(0, 1);
}
QString filePath;
if (QFileInfo(_documentRoot + subPath).isFile()) {
filePath = _documentRoot + subPath;
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
// this could be a directory with a trailing slash
// send a redirect to the path with a slash so we can
QString redirectLocation = '/' + subPath + '/';
if (!url.query().isEmpty()) {
redirectLocation += "?" + url.query();
}
QHash<QByteArray, QByteArray> redirectHeader;
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
}
// if the last thing is a trailing slash then we want to look for index file
if (subPath.endsWith('/') || subPath.size() == 0) {
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
filePath = _documentRoot + subPath + possibleIndexFilename;
break;
}
}
}
if (!filePath.isEmpty()) {
// file exists, serve it
static QMimeDatabase mimeDatabase;
QFile localFile(filePath);
localFile.open(QIODevice::ReadOnly);
QByteArray localFileData = localFile.readAll();
QFileInfo localFileInfo(filePath);
if (localFileInfo.completeSuffix() == "shtml") {
// this is a file that may have some SSI statements
// the only thing we support is the include directive, but check the contents for that
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
QRegExp includeRegExp(includeRegExpString);
int matchPosition = 0;
QString localFileString(localFileData);
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
// check if this is a file or vitual include
bool isFileInclude = includeRegExp.cap(1) == "file";
// setup the correct file path for the included file
QString includeFilePath = isFileInclude
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
: _documentRoot + includeRegExp.cap(2);
QString replacementString;
if (QFileInfo(includeFilePath).isFile()) {
QFile includedFile(includeFilePath);
includedFile.open(QIODevice::ReadOnly);
replacementString = QString(includedFile.readAll());
} else {
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
}
// replace the match with the contents of the file, or an empty string if the file was not found
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
// push the match position forward so we can check the next match
matchPosition += includeRegExp.matchedLength();
}
localFileData = localFileString.toLocal8Bit();
}
connection->respond(HTTPConnection::StatusCode200, localFileData,
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
return true;
}
}
// respond with a 404
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
return true;
}
bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
return _requestHandler && _requestHandler->handleHTTPRequest(connection, url);
}

View file

@ -19,6 +19,7 @@
#include <QtNetwork/QTcpServer> #include <QtNetwork/QTcpServer>
class HTTPConnection; class HTTPConnection;
class HTTPSConnection;
class HTTPRequestHandler { class HTTPRequestHandler {
public: public:
@ -35,9 +36,10 @@ public:
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
protected slots: protected:
/// Accepts all pending connections /// Accepts all pending connections
void acceptConnections(); virtual void incomingConnection(qintptr socketDescriptor);
virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
protected: protected:
QString _documentRoot; QString _documentRoot;
HTTPRequestHandler* _requestHandler; HTTPRequestHandler* _requestHandler;

View file

@ -0,0 +1,23 @@
//
// HTTPSConnection.cpp
// libraries/embedded-webserver/src
//
// Created by Stephen Birarda on 2014-04-24.
// 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 "HTTPSConnection.h"
HTTPSConnection::HTTPSConnection(QSslSocket* sslSocket, HTTPSManager* parentManager) :
HTTPConnection(sslSocket, parentManager)
{
connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError>&)), this, SLOT(handleSSLErrors(const QList<QSslError>&)));
sslSocket->startServerEncryption();
}
void HTTPSConnection::handleSSLErrors(const QList<QSslError>& errors) {
qDebug() << "SSL errors:" << errors;
}

View file

@ -0,0 +1,26 @@
//
// HTTPSConnection.h
// libraries/embedded-webserver/src
//
// Created by Stephen Birarda on 2014-04-24.
// 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_HTTPSConnection_h
#define hifi_HTTPSConnection_h
#include "HTTPConnection.h"
#include "HTTPSManager.h"
class HTTPSConnection : public HTTPConnection {
Q_OBJECT
public:
HTTPSConnection(QSslSocket* sslSocket, HTTPSManager* parentManager);
protected slots:
void handleSSLErrors(const QList<QSslError>& errors);
};
#endif // hifi_HTTPSConnection_h

View file

@ -0,0 +1,51 @@
//
// HTTPSManager.cpp
// libraries/embedded-webserver/src
//
// Created by Stephen Birarda on 2014-04-24.
// 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 <QtNetwork/QSslSocket>
#include "HTTPSConnection.h"
#include "HTTPSManager.h"
HTTPSManager::HTTPSManager(quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey,
const QString& documentRoot, HTTPSRequestHandler* requestHandler, QObject* parent) :
HTTPManager(port, documentRoot, requestHandler, parent),
_certificate(certificate),
_privateKey(privateKey),
_sslRequestHandler(requestHandler)
{
}
void HTTPSManager::incomingConnection(qintptr socketDescriptor) {
QSslSocket* sslSocket = new QSslSocket(this);
sslSocket->setLocalCertificate(_certificate);
sslSocket->setPrivateKey(_privateKey);
if (sslSocket->setSocketDescriptor(socketDescriptor)) {
new HTTPSConnection(sslSocket, this);
} else {
delete sslSocket;
}
}
bool HTTPSManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) {
return handleHTTPSRequest(reinterpret_cast<HTTPSConnection*>(connection), url);
}
bool HTTPSManager::handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url) {
return HTTPManager::handleHTTPRequest(connection, url);
}
bool HTTPSManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
return _sslRequestHandler && _sslRequestHandler->handleHTTPSRequest(reinterpret_cast<HTTPSConnection*>(connection), url);
}

View file

@ -0,0 +1,50 @@
//
// HTTPSManager.h
// libraries/embedded-webserver/src
//
// Created by Stephen Birarda on 2014-04-24.
// 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_HTTPSManager_h
#define hifi_HTTPSManager_h
#include <QtNetwork/QSslKey>
#include <QtNetwork/QSslCertificate>
#include "HTTPManager.h"
class HTTPSRequestHandler : public HTTPRequestHandler {
public:
/// Handles an HTTPS request
virtual bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url) = 0;
};
class HTTPSManager : public HTTPManager, public HTTPSRequestHandler {
Q_OBJECT
public:
HTTPSManager(quint16 port,
const QSslCertificate& certificate,
const QSslKey& privateKey,
const QString& documentRoot,
HTTPSRequestHandler* requestHandler = NULL, QObject* parent = 0);
void setCertificate(const QSslCertificate& certificate) { _certificate = certificate; }
void setPrivateKey(const QSslKey& privateKey) { _privateKey = privateKey; }
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
protected:
void incomingConnection(qintptr socketDescriptor);
bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
private:
QSslCertificate _certificate;
QSslKey _privateKey;
HTTPSRequestHandler* _sslRequestHandler;
};
#endif // hifi_HTTPSManager_h

View file

@ -406,7 +406,7 @@ QVariantHash parseMapping(QIODevice* device) {
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) { QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec3> values; QVector<glm::vec3> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 3 * 3); it != end; ) {
float x = *it++; float x = *it++;
float y = *it++; float y = *it++;
float z = *it++; float z = *it++;
@ -417,7 +417,7 @@ QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec2> createVec2Vector(const QVector<double>& doubleVector) { QVector<glm::vec2> createVec2Vector(const QVector<double>& doubleVector) {
QVector<glm::vec2> values; QVector<glm::vec2> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) { for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 2 * 2); it != end; ) {
float s = *it++; float s = *it++;
float t = *it++; float t = *it++;
values.append(glm::vec2(s, -t)); values.append(glm::vec2(s, -t));
@ -432,58 +432,59 @@ glm::mat4 createMat4(const QVector<double>& doubleVector) {
doubleVector.at(12), doubleVector.at(13), doubleVector.at(14), doubleVector.at(15)); doubleVector.at(12), doubleVector.at(13), doubleVector.at(14), doubleVector.at(15));
} }
QVector<int> getIntVector(const QVariantList& properties, int index) { QVector<int> getIntVector(const FBXNode& node) {
if (index >= properties.size()) { foreach (const FBXNode& child, node.children) {
if (child.name == "a") {
return getIntVector(child);
}
}
if (node.properties.isEmpty()) {
return QVector<int>(); return QVector<int>();
} }
QVector<int> vector = properties.at(index).value<QVector<int> >(); QVector<int> vector = node.properties.at(0).value<QVector<int> >();
if (!vector.isEmpty()) { if (!vector.isEmpty()) {
return vector; return vector;
} }
for (; index < properties.size(); index++) { for (int i = 0; i < node.properties.size(); i++) {
vector.append(properties.at(index).toInt()); vector.append(node.properties.at(i).toInt());
} }
return vector; return vector;
} }
QVector<qlonglong> getLongVector(const QVariantList& properties, int index) { QVector<float> getFloatVector(const FBXNode& node) {
if (index >= properties.size()) { foreach (const FBXNode& child, node.children) {
return QVector<qlonglong>(); if (child.name == "a") {
return getFloatVector(child);
}
} }
QVector<qlonglong> vector = properties.at(index).value<QVector<qlonglong> >(); if (node.properties.isEmpty()) {
if (!vector.isEmpty()) {
return vector;
}
for (; index < properties.size(); index++) {
vector.append(properties.at(index).toLongLong());
}
return vector;
}
QVector<float> getFloatVector(const QVariantList& properties, int index) {
if (index >= properties.size()) {
return QVector<float>(); return QVector<float>();
} }
QVector<float> vector = properties.at(index).value<QVector<float> >(); QVector<float> vector = node.properties.at(0).value<QVector<float> >();
if (!vector.isEmpty()) { if (!vector.isEmpty()) {
return vector; return vector;
} }
for (; index < properties.size(); index++) { for (int i = 0; i < node.properties.size(); i++) {
vector.append(properties.at(index).toFloat()); vector.append(node.properties.at(i).toFloat());
} }
return vector; return vector;
} }
QVector<double> getDoubleVector(const QVariantList& properties, int index) { QVector<double> getDoubleVector(const FBXNode& node) {
if (index >= properties.size()) { foreach (const FBXNode& child, node.children) {
if (child.name == "a") {
return getDoubleVector(child);
}
}
if (node.properties.isEmpty()) {
return QVector<double>(); return QVector<double>();
} }
QVector<double> vector = properties.at(index).value<QVector<double> >(); QVector<double> vector = node.properties.at(0).value<QVector<double> >();
if (!vector.isEmpty()) { if (!vector.isEmpty()) {
return vector; return vector;
} }
for (; index < properties.size(); index++) { for (int i = 0; i < node.properties.size(); i++) {
vector.append(properties.at(index).toDouble()); vector.append(node.properties.at(i).toDouble());
} }
return vector; return vector;
} }
@ -697,21 +698,30 @@ public:
}; };
void appendIndex(MeshData& data, QVector<int>& indices, int index) { void appendIndex(MeshData& data, QVector<int>& indices, int index) {
if (index >= data.polygonIndices.size()) {
return;
}
int vertexIndex = data.polygonIndices.at(index); int vertexIndex = data.polygonIndices.at(index);
if (vertexIndex < 0) { if (vertexIndex < 0) {
vertexIndex = -vertexIndex - 1; vertexIndex = -vertexIndex - 1;
} }
Vertex vertex; Vertex vertex;
vertex.originalIndex = vertexIndex; vertex.originalIndex = vertexIndex;
glm::vec3 position;
if (vertexIndex < data.vertices.size()) {
position = data.vertices.at(vertexIndex);
}
glm::vec3 normal; glm::vec3 normal;
if (data.normalIndices.isEmpty()) { int normalIndex = data.normalsByVertex ? vertexIndex : index;
normal = data.normals.at(data.normalsByVertex ? vertexIndex : index); if (data.normalIndices.isEmpty()) {
if (normalIndex < data.normals.size()) {
} else { normal = data.normals.at(normalIndex);
int normalIndex = data.normalIndices.at(data.normalsByVertex ? vertexIndex : index); }
if (normalIndex >= 0) { } else if (normalIndex < data.normalIndices.size()) {
normalIndex = data.normalIndices.at(normalIndex);
if (normalIndex >= 0 && normalIndex < data.normals.size()) {
normal = data.normals.at(normalIndex); normal = data.normals.at(normalIndex);
} }
} }
@ -720,9 +730,9 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
if (index < data.texCoords.size()) { if (index < data.texCoords.size()) {
vertex.texCoord = data.texCoords.at(index); vertex.texCoord = data.texCoords.at(index);
} }
} else { } else if (index < data.texCoordIndices.size()) {
int texCoordIndex = data.texCoordIndices.at(index); int texCoordIndex = data.texCoordIndices.at(index);
if (texCoordIndex >= 0) { if (texCoordIndex >= 0 && texCoordIndex < data.texCoords.size()) {
vertex.texCoord = data.texCoords.at(texCoordIndex); vertex.texCoord = data.texCoords.at(texCoordIndex);
} }
} }
@ -733,7 +743,7 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
indices.append(newIndex); indices.append(newIndex);
data.indices.insert(vertex, newIndex); data.indices.insert(vertex, newIndex);
data.extracted.newIndices.insert(vertexIndex, newIndex); data.extracted.newIndices.insert(vertexIndex, newIndex);
data.extracted.mesh.vertices.append(data.vertices.at(vertexIndex)); data.extracted.mesh.vertices.append(position);
data.extracted.mesh.normals.append(normal); data.extracted.mesh.normals.append(normal);
data.extracted.mesh.texCoords.append(vertex.texCoord); data.extracted.mesh.texCoords.append(vertex.texCoord);
@ -749,44 +759,51 @@ ExtractedMesh extractMesh(const FBXNode& object) {
QVector<int> textures; QVector<int> textures;
foreach (const FBXNode& child, object.children) { foreach (const FBXNode& child, object.children) {
if (child.name == "Vertices") { if (child.name == "Vertices") {
data.vertices = createVec3Vector(getDoubleVector(child.properties, 0)); data.vertices = createVec3Vector(getDoubleVector(child));
} else if (child.name == "PolygonVertexIndex") { } else if (child.name == "PolygonVertexIndex") {
data.polygonIndices = getIntVector(child.properties, 0); data.polygonIndices = getIntVector(child);
} else if (child.name == "LayerElementNormal") { } else if (child.name == "LayerElementNormal") {
data.normalsByVertex = false; data.normalsByVertex = false;
bool indexToDirect = false;
foreach (const FBXNode& subdata, child.children) { foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "Normals") { if (subdata.name == "Normals") {
data.normals = createVec3Vector(getDoubleVector(subdata.properties, 0)); data.normals = createVec3Vector(getDoubleVector(subdata));
} else if (subdata.name == "NormalsIndex") { } else if (subdata.name == "NormalsIndex") {
data.normalIndices = getIntVector(subdata.properties, 0); data.normalIndices = getIntVector(subdata);
} else if (subdata.name == "MappingInformationType" && } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == "ByVertice") {
subdata.properties.at(0) == "ByVertice") {
data.normalsByVertex = true; data.normalsByVertex = true;
} else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0) == "IndexToDirect") {
indexToDirect = true;
} }
} }
if (indexToDirect && data.normalIndices.isEmpty()) {
// hack to work around wacky Makehuman exports
data.normalsByVertex = true;
}
} else if (child.name == "LayerElementUV" && child.properties.at(0).toInt() == 0) { } else if (child.name == "LayerElementUV" && child.properties.at(0).toInt() == 0) {
foreach (const FBXNode& subdata, child.children) { foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "UV") { if (subdata.name == "UV") {
data.texCoords = createVec2Vector(getDoubleVector(subdata.properties, 0)); data.texCoords = createVec2Vector(getDoubleVector(subdata));
} else if (subdata.name == "UVIndex") { } else if (subdata.name == "UVIndex") {
data.texCoordIndices = getIntVector(subdata.properties, 0); data.texCoordIndices = getIntVector(subdata);
} }
} }
} else if (child.name == "LayerElementMaterial") { } else if (child.name == "LayerElementMaterial") {
foreach (const FBXNode& subdata, child.children) { foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "Materials") { if (subdata.name == "Materials") {
materials = getIntVector(subdata.properties, 0); materials = getIntVector(subdata);
} }
} }
} else if (child.name == "LayerElementTexture") { } else if (child.name == "LayerElementTexture") {
foreach (const FBXNode& subdata, child.children) { foreach (const FBXNode& subdata, child.children) {
if (subdata.name == "TextureId") { if (subdata.name == "TextureId") {
textures = getIntVector(subdata.properties, 0); textures = getIntVector(subdata);
} }
} }
} }
@ -797,7 +814,7 @@ ExtractedMesh extractMesh(const FBXNode& object) {
QHash<QPair<int, int>, int> materialTextureParts; QHash<QPair<int, int>, int> materialTextureParts;
for (int beginIndex = 0; beginIndex < data.polygonIndices.size(); polygonIndex++) { for (int beginIndex = 0; beginIndex < data.polygonIndices.size(); polygonIndex++) {
int endIndex = beginIndex; int endIndex = beginIndex;
while (data.polygonIndices.at(endIndex++) >= 0); while (endIndex < data.polygonIndices.size() && data.polygonIndices.at(endIndex++) >= 0);
QPair<int, int> materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0, QPair<int, int> materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0,
(polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0); (polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0);
@ -820,7 +837,7 @@ ExtractedMesh extractMesh(const FBXNode& object) {
appendIndex(data, part.triangleIndices, beginIndex); appendIndex(data, part.triangleIndices, beginIndex);
appendIndex(data, part.triangleIndices, nextIndex++); appendIndex(data, part.triangleIndices, nextIndex++);
appendIndex(data, part.triangleIndices, nextIndex); appendIndex(data, part.triangleIndices, nextIndex);
if (data.polygonIndices.at(nextIndex) < 0) { if (nextIndex >= data.polygonIndices.size() || data.polygonIndices.at(nextIndex) < 0) {
break; break;
} }
} }
@ -835,13 +852,13 @@ FBXBlendshape extractBlendshape(const FBXNode& object) {
FBXBlendshape blendshape; FBXBlendshape blendshape;
foreach (const FBXNode& data, object.children) { foreach (const FBXNode& data, object.children) {
if (data.name == "Indexes") { if (data.name == "Indexes") {
blendshape.indices = getIntVector(data.properties, 0); blendshape.indices = getIntVector(data);
} else if (data.name == "Vertices") { } else if (data.name == "Vertices") {
blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0)); blendshape.vertices = createVec3Vector(getDoubleVector(data));
} else if (data.name == "Normals") { } else if (data.name == "Normals") {
blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0)); blendshape.normals = createVec3Vector(getDoubleVector(data));
} }
} }
return blendshape; return blendshape;
@ -1016,7 +1033,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
foreach (const FBXNode& object, child.children) { foreach (const FBXNode& object, child.children) {
if (object.name == "SceneInfo") { if (object.name == "SceneInfo") {
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Properties70") { if (subobject.name == "MetaData") {
foreach (const FBXNode& subsubobject, subobject.children) {
if (subsubobject.name == "Author") {
geometry.author = subsubobject.properties.at(0).toString();
}
}
} else if (subobject.name == "Properties70") {
foreach (const FBXNode& subsubobject, subobject.children) { foreach (const FBXNode& subsubobject, subobject.children) {
if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 && if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 &&
subsubobject.properties.at(0) == "Original|ApplicationName") { subsubobject.properties.at(0) == "Original|ApplicationName") {
@ -1262,13 +1285,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
Cluster cluster; Cluster cluster;
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Indexes") { if (subobject.name == "Indexes") {
cluster.indices = getIntVector(subobject.properties, 0); cluster.indices = getIntVector(subobject);
} else if (subobject.name == "Weights") { } else if (subobject.name == "Weights") {
cluster.weights = getDoubleVector(subobject.properties, 0); cluster.weights = getDoubleVector(subobject);
} else if (subobject.name == "TransformLink") { } else if (subobject.name == "TransformLink") {
QVector<double> values = getDoubleVector(subobject.properties, 0); QVector<double> values = getDoubleVector(subobject);
cluster.transformLink = createMat4(values); cluster.transformLink = createMat4(values);
} }
} }
@ -1290,7 +1313,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
AnimationCurve curve; AnimationCurve curve;
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "KeyValueFloat") { if (subobject.name == "KeyValueFloat") {
curve.values = getFloatVector(subobject.properties, 0); curve.values = getFloatVector(subobject);
} }
} }
animationCurves.insert(getID(object.properties), curve); animationCurves.insert(getID(object.properties), curve);

View file

@ -47,6 +47,9 @@ public:
/// \return true if point is within current limits /// \return true if point is within current limits
bool containsPoint(const glm::vec3& point) const; bool containsPoint(const glm::vec3& point) const;
/// \return whether or not the extents are empty
bool isEmpty() { return minimum == maximum; }
glm::vec3 minimum; glm::vec3 minimum;
glm::vec3 maximum; glm::vec3 maximum;
}; };
@ -174,6 +177,7 @@ public:
class FBXGeometry { class FBXGeometry {
public: public:
QString author;
QString applicationName; ///< the name of the application that generated the model QString applicationName; ///< the name of the application that generated the model
QVector<FBXJoint> joints; QVector<FBXJoint> joints;

View file

@ -55,9 +55,7 @@ public:
void requestAccessToken(const QString& login, const QString& password); void requestAccessToken(const QString& login, const QString& password);
QString getUsername() const { return _accountInfo.getUsername(); } const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
const QString& getXMPPPassword() const { return _accountInfo.getXMPPPassword(); }
void destroy() { delete _networkAccessManager; } void destroy() { delete _networkAccessManager; }

View file

@ -50,7 +50,8 @@ Assignment::Assignment() :
_type(Assignment::AllTypes), _type(Assignment::AllTypes),
_pool(), _pool(),
_location(Assignment::LocalLocation), _location(Assignment::LocalLocation),
_payload() _payload(),
_isStatic(false)
{ {
} }
@ -61,7 +62,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
_type(type), _type(type),
_pool(pool), _pool(pool),
_location(location), _location(location),
_payload() _payload(),
_isStatic(false)
{ {
if (_command == Assignment::CreateCommand) { if (_command == Assignment::CreateCommand) {
// this is a newly created assignment, generate a random UUID // this is a newly created assignment, generate a random UUID

View file

@ -78,6 +78,9 @@ public:
void setPool(const QString& pool) { _pool = pool; }; void setPool(const QString& pool) { _pool = pool; };
const QString& getPool() const { return _pool; } const QString& getPool() const { return _pool; }
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
bool isStatic() const { return _isStatic; }
const char* getTypeName() const; const char* getTypeName() const;
// implement parseData to return 0 so we can be a subclass of NodeData // implement parseData to return 0 so we can be a subclass of NodeData
@ -94,6 +97,7 @@ protected:
QString _pool; /// the destination pool for this assignment QString _pool; /// the destination pool for this assignment
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
}; };
#endif // hifi_Assignment_h #endif // hifi_Assignment_h

View file

@ -78,7 +78,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() {
static bool datumInitialized = false; static bool datumInitialized = false;
static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] = static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] =
"-----BEGIN CERTIFICATE-----" "-----BEGIN CERTIFICATE-----\n"
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n" "MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n" "aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
@ -100,7 +100,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() {
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n" "SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n" "FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n" "Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
"-----END CERTIFICATE-----"; "-----END CERTIFICATE-----\n";
if (!datumInitialized) { if (!datumInitialized) {
hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT; hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT;

View file

@ -22,8 +22,11 @@
#include "HifiSockAddr.h" #include "HifiSockAddr.h"
const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io"; const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io";
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103;
const quint16 DOMAIN_SERVER_HTTP_PORT = 40100;
const quint16 DOMAIN_SERVER_HTTPS_PORT = 40101;
class DomainHandler : public QObject { class DomainHandler : public QObject {
Q_OBJECT Q_OBJECT

View file

@ -106,6 +106,10 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() {
_dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress); _dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress);
// we're using DTLS and our socket is good to go, so make the required DTLS changes
// DTLS requires that IP_DONTFRAG be set
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
#if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER) #if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER)
qDebug() << "Making required DTLS changes to LimitedNodeList DTLS socket."; qDebug() << "Making required DTLS changes to LimitedNodeList DTLS socket.";

View file

@ -378,12 +378,17 @@ void NodeList::sendDomainServerCheckIn() {
} }
} }
PacketType domainPacketType = _sessionUUID.isNull() PacketType domainPacketType = !_domainHandler.isConnected()
? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
// construct the DS check in packet // construct the DS check in packet
QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest QUuid packetUUID = _sessionUUID;
? _sessionUUID : _domainHandler.getAssignmentUUID());
if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) {
// this is a connect request and we're an assigned node
// so set our packetUUID as the assignment UUID
packetUUID = _domainHandler.getAssignmentUUID();
}
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append); QDataStream packetStream(&domainServerPacket, QIODevice::Append);
@ -404,7 +409,6 @@ void NodeList::sendDomainServerCheckIn() {
dtlsSession->writeDatagram(domainServerPacket); dtlsSession->writeDatagram(domainServerPacket);
} }
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0; static unsigned int numDomainCheckins = 0;

View file

@ -38,7 +38,7 @@ enum PacketType {
PacketTypeDomainListRequest, PacketTypeDomainListRequest,
PacketTypeRequestAssignment, PacketTypeRequestAssignment,
PacketTypeCreateAssignment, PacketTypeCreateAssignment,
PacketTypeDataServerPut, // reusable PacketTypeDomainOAuthRequest,
PacketTypeDataServerGet, // reusable PacketTypeDataServerGet, // reusable
PacketTypeDataServerSend, // reusable PacketTypeDataServerSend, // reusable
PacketTypeDataServerConfirm, PacketTypeDataServerConfirm,
@ -72,7 +72,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 << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery; << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery;

View file

@ -17,6 +17,10 @@ glm::vec3 Vec3::cross(const glm::vec3& v1, const glm::vec3& v2) {
return glm::cross(v1,v2); return glm::cross(v1,v2);
} }
float Vec3::dot(const glm::vec3& v1, const glm::vec3& v2) {
return glm::dot(v1,v2);
}
glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) { glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) {
return v1 * f; return v1 * f;
} }

View file

@ -26,6 +26,7 @@ class Vec3 : public QObject {
public slots: public slots:
glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2);
float dot(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 multiply(const glm::vec3& v1, float f); glm::vec3 multiply(const glm::vec3& v1, float f);
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v); glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);