mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 13:53:38 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into modelserver
This commit is contained in:
commit
3bb72c04a5
48 changed files with 1384 additions and 497 deletions
|
@ -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
|
||||
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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
./assignment-client -n 5
|
||||
./assignment-client -n 6
|
||||
|
||||
To test things out you'll want to run the Interface client.
|
||||
|
||||
|
|
|
@ -191,9 +191,10 @@ void Agent::run() {
|
|||
// figure out the URL for the script for this agent assignment
|
||||
QUrl scriptURL;
|
||||
if (_payload.isEmpty()) {
|
||||
scriptURL = QUrl(QString("http://%1:8080/assignment/%2")
|
||||
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(),
|
||||
uuidStringWithoutCurlyBraces(_uuid)));
|
||||
scriptURL = QUrl(QString("http://%1:%2/assignment/%3")
|
||||
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString())
|
||||
.arg(DOMAIN_SERVER_HTTP_PORT)
|
||||
.arg(uuidStringWithoutCurlyBraces(_uuid)));
|
||||
} else {
|
||||
scriptURL = QUrl(_payload);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QProcess>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUrlQuery>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
|
@ -31,18 +32,22 @@
|
|||
|
||||
#include "DomainServer.h"
|
||||
|
||||
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_staticAssignmentHash(),
|
||||
_assignmentQueue(),
|
||||
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_httpsManager(NULL),
|
||||
_allAssignments(),
|
||||
_unfulfilledAssignments(),
|
||||
_isUsingDTLS(false),
|
||||
_x509Credentials(NULL),
|
||||
_dhParams(NULL),
|
||||
_priorityCache(NULL),
|
||||
_dtlsSessions()
|
||||
_dtlsSessions(),
|
||||
_oauthProviderURL(),
|
||||
_oauthClientID(),
|
||||
_hostname(),
|
||||
_networkReplyUUIDMap(),
|
||||
_sessionAuthenticationHash()
|
||||
{
|
||||
gnutls_global_init();
|
||||
|
||||
|
@ -53,22 +58,20 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
_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
|
||||
// and set up the node list
|
||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
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();
|
||||
|
||||
// connect our socket to read datagrams received on the DTLS socket
|
||||
connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams);
|
||||
}
|
||||
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,44 +89,7 @@ DomainServer::~DomainServer() {
|
|||
gnutls_global_deinit();
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupDTLS() {
|
||||
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() {
|
||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
|
||||
|
@ -157,6 +123,22 @@ bool DomainServer::readX509KeyAndCertificate() {
|
|||
|
||||
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()) {
|
||||
qDebug() << "Missing certificate or private key. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
|
@ -166,6 +148,67 @@ bool DomainServer::readX509KeyAndCertificate() {
|
|||
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) {
|
||||
|
||||
const QString CUSTOM_PORT_OPTION = "port";
|
||||
|
@ -246,7 +289,8 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
|
|||
|
||||
void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) {
|
||||
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) {
|
||||
|
@ -279,7 +323,9 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr
|
|||
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
|
||||
_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
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::MetavoxelServer;
|
||||
|
||||
void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
NodeType_t nodeType;
|
||||
HifiSockAddr publicSockAddr, localSockAddr;
|
||||
|
||||
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
|
||||
|
||||
QUuid assignmentUUID = uuidFromPacketHeader(packet);
|
||||
bool isStaticAssignment = _staticAssignmentHash.contains(assignmentUUID);
|
||||
SharedAssignmentPointer matchingAssignment = SharedAssignmentPointer();
|
||||
|
||||
if (isStaticAssignment) {
|
||||
// this is a static assignment, make sure the UUID sent is for an assignment we're actually trying to give out
|
||||
matchingAssignment = matchingQueuedAssignmentForCheckIn(assignmentUUID, 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();
|
||||
QUuid packetUUID = uuidFromPacketHeader(packet);
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
|
||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
|
||||
}
|
||||
|
||||
// make sure this was either not a static assignment or it was and we had a matching one in teh queue
|
||||
if ((!isStaticAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) || (isStaticAssignment && matchingAssignment)) {
|
||||
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
|
||||
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
||||
if (!_sessionAuthenticationHash.value(packetUUID)) {
|
||||
// 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
|
||||
QUuid nodeUUID = QUuid::createUuid();
|
||||
|
||||
SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType,
|
||||
publicSockAddr, localSockAddr);
|
||||
|
||||
// when the newNode is created the linked data is also created
|
||||
// if this was a static assignment set the UUID, set the sendingSockAddr
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
nodeData->setStaticAssignmentUUID(assignmentUUID);
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
nodeData->setAssignmentUUID(packetUUID);
|
||||
}
|
||||
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
||||
// 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,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
@ -451,52 +555,54 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
|
||||
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
|
||||
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())) {
|
||||
if (nodeData->isAuthenticated()) {
|
||||
// if this authenticated node has any interest types, send back those nodes as well
|
||||
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
|
||||
|
||||
// don't send avatar nodes to other avatars, that will come from avatar mixer
|
||||
nodeDataStream << *otherNode.data();
|
||||
// reset our nodeByteArray and nodeDataStream
|
||||
QByteArray nodeByteArray;
|
||||
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
|
||||
|
||||
// pack the secret that these two nodes will use to communicate with each other
|
||||
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
|
||||
if (secretUUID.isNull()) {
|
||||
// generate a new secret UUID these two nodes can use
|
||||
secretUUID = QUuid::createUuid();
|
||||
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
|
||||
|
||||
// set that on the current Node's sessionSecretHash
|
||||
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
|
||||
// don't send avatar nodes to other avatars, that will come from avatar mixer
|
||||
nodeDataStream << *otherNode.data();
|
||||
|
||||
// set it on the other Node's sessionSecretHash
|
||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
|
||||
// pack the secret that these two nodes will use to communicate with each other
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
broadcastPacket.resize(numBroadcastPacketLeadBytes);
|
||||
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
|
||||
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
|
||||
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);
|
||||
|
||||
if (requestType == PacketTypeDomainConnectRequest) {
|
||||
// add this node to our NodeList
|
||||
// and send back session UUID right away
|
||||
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
|
||||
handleConnectRequest(receivedPacket, senderSockAddr);
|
||||
} else if (requestType == PacketTypeDomainListRequest) {
|
||||
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
|
||||
|
||||
|
@ -738,7 +842,8 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
|||
nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp());
|
||||
|
||||
// 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) {
|
||||
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
|
||||
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
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID());
|
||||
assignedNodesJSON[uuidString] = jsonObjectForNode(node);
|
||||
}
|
||||
}
|
||||
|
@ -786,7 +893,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
QJsonObject queuedAssignmentsJSON;
|
||||
|
||||
// add the queued but unfilled assignments to the json
|
||||
foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) {
|
||||
foreach(const SharedAssignmentPointer& assignment, _unfulfilledAssignments) {
|
||||
QJsonObject queuedAssignmentJSON;
|
||||
|
||||
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));
|
||||
|
||||
// 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
|
||||
|
@ -950,6 +1059,114 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
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) {
|
||||
QUuid oldUUID = assignment->getUUID();
|
||||
assignment->resetUUID();
|
||||
|
@ -963,13 +1180,8 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer&
|
|||
}
|
||||
|
||||
// add the static assignment back under the right UUID, and to the queue
|
||||
_staticAssignmentHash.insert(assignment->getUUID(), 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);
|
||||
_allAssignments.insert(assignment->getUUID(), assignment);
|
||||
_unfulfilledAssignments.enqueue(assignment);
|
||||
}
|
||||
|
||||
void DomainServer::nodeAdded(SharedNodePointer node) {
|
||||
|
@ -983,10 +1195,10 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
|
||||
if (nodeData) {
|
||||
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
|
||||
if (!nodeData->getStaticAssignmentUUID().isNull()) {
|
||||
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(nodeData->getStaticAssignmentUUID());
|
||||
if (!nodeData->getAssignmentUUID().isNull()) {
|
||||
SharedAssignmentPointer matchedAssignment = _allAssignments.take(nodeData->getAssignmentUUID());
|
||||
|
||||
if (matchedAssignment) {
|
||||
if (matchedAssignment && matchedAssignment->isStatic()) {
|
||||
refreshStaticAssignmentAndAddToQueue(matchedAssignment);
|
||||
}
|
||||
}
|
||||
|
@ -1010,11 +1222,11 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
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) {
|
||||
return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
|
||||
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
|
@ -1026,9 +1238,9 @@ SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const Q
|
|||
SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) {
|
||||
// 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
|
||||
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _assignmentQueue.begin();
|
||||
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _unfulfilledAssignments.begin();
|
||||
|
||||
while (sharedAssignment != _assignmentQueue.end()) {
|
||||
while (sharedAssignment != _unfulfilledAssignments.end()) {
|
||||
Assignment* assignment = sharedAssignment->data();
|
||||
bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes;
|
||||
bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType();
|
||||
|
@ -1038,16 +1250,12 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) {
|
||||
|
||||
// remove the assignment from the queue
|
||||
SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment
|
||||
- _assignmentQueue.begin());
|
||||
SharedAssignmentPointer deployableAssignment = _unfulfilledAssignments.takeAt(sharedAssignment
|
||||
- _unfulfilledAssignments.begin());
|
||||
|
||||
if (deployableAssignment->getType() != Assignment::AgentType
|
||||
|| _staticAssignmentHash.contains(deployableAssignment->getUUID())) {
|
||||
// this is a static assignment
|
||||
// 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);
|
||||
}
|
||||
// until we get a connection for this assignment
|
||||
// put assignment back in queue but stick it at the back so the others have a chance to go out
|
||||
_unfulfilledAssignments.enqueue(deployableAssignment);
|
||||
|
||||
// stop looping, we've handed out an assignment
|
||||
return deployableAssignment;
|
||||
|
@ -1061,10 +1269,10 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
}
|
||||
|
||||
void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _assignmentQueue.begin();
|
||||
while (potentialMatchingAssignment != _assignmentQueue.end()) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin();
|
||||
while (potentialMatchingAssignment != _unfulfilledAssignments.end()) {
|
||||
if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
|
||||
_assignmentQueue.erase(potentialMatchingAssignment);
|
||||
_unfulfilledAssignments.erase(potentialMatchingAssignment);
|
||||
|
||||
// we matched and removed an assignment, bail out
|
||||
break;
|
||||
|
@ -1078,7 +1286,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
|
||||
// if the domain-server has just restarted,
|
||||
// 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();
|
||||
while (staticAssignment != staticHashCopy.end()) {
|
||||
// add any of the un-matched static assignments to the queue
|
||||
|
|
|
@ -23,20 +23,21 @@
|
|||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <HTTPSConnection.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DTLSServerSession.h"
|
||||
|
||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||
|
||||
class DomainServer : public QCoreApplication, public HTTPRequestHandler {
|
||||
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
||||
|
||||
void exit(int retCode = 0);
|
||||
|
||||
|
@ -52,12 +53,13 @@ private slots:
|
|||
void readAvailableDTLSDatagrams();
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallySetupDTLS();
|
||||
bool readX509KeyAndCertificate();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
|
||||
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,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||
|
@ -76,13 +78,20 @@ private:
|
|||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
void handleProfileRequestFinished();
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
HTTPManager _HTTPManager;
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
|
||||
QQueue<SharedAssignmentPointer> _assignmentQueue;
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
|
||||
QVariantMap _argumentVariantMap;
|
||||
|
||||
|
@ -93,6 +102,15 @@ private:
|
|||
gnutls_priority_t* _priorityCache;
|
||||
|
||||
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
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
DomainServerNodeData::DomainServerNodeData() :
|
||||
_sessionSecretHash(),
|
||||
_staticAssignmentUUID(),
|
||||
_assignmentUUID(),
|
||||
_statsJSONObject(),
|
||||
_sendingSockAddr()
|
||||
_sendingSockAddr(),
|
||||
_isAuthenticated(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -27,20 +27,24 @@ public:
|
|||
|
||||
void parseJSONStatsPacket(const QByteArray& statsPacket);
|
||||
|
||||
void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; }
|
||||
const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; }
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
|
||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
||||
|
||||
void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; }
|
||||
bool isAuthenticated() const { return _isAuthenticated; }
|
||||
|
||||
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
|
||||
private:
|
||||
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
|
||||
|
||||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _staticAssignmentUUID;
|
||||
QUuid _assignmentUUID;
|
||||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
bool _isAuthenticated;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
342
examples/editModels.js
Normal file
342
examples/editModels.js
Normal 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);
|
||||
|
||||
|
||||
|
|
@ -81,9 +81,10 @@
|
|||
#include "scripting/LocationScriptingInterface.h"
|
||||
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
#include "ui/Snapshot.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
#include "ui/Stats.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -361,12 +362,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
QMutexLocker locker(&_settingsMutex);
|
||||
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
|
||||
}
|
||||
|
||||
|
||||
connect(_window, &MainWindow::windowGeometryChanged,
|
||||
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
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() {
|
||||
|
@ -555,41 +561,6 @@ void Application::paintGL() {
|
|||
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
|
||||
_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) {
|
||||
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
|
||||
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
|
||||
|
@ -3124,9 +3095,9 @@ void Application::updateWindowTitle(){
|
|||
QString buildVersion = " (build " + applicationVersion() + ")";
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
QString username = AccountManager::getInstance().getUsername();
|
||||
QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString()
|
||||
+ " @ " + nodeList->getDomainHandler().getHostname() + buildVersion;
|
||||
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
|
||||
+ nodeList->getDomainHandler().getHostname() + buildVersion;
|
||||
qDebug("Application title set to: %s", title.toStdString().c_str());
|
||||
_window->setWindowTitle(title);
|
||||
}
|
||||
|
@ -3150,6 +3121,9 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
|
||||
// reset the voxels renderer
|
||||
_voxels.killLocalVoxels();
|
||||
|
||||
// reset the auth URL for OAuth web view handler
|
||||
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
|
||||
#include "DatagramProcessor.h"
|
||||
|
||||
|
@ -56,13 +57,11 @@ void DatagramProcessor::processDatagrams() {
|
|||
Particle::handleAddParticleResponse(incomingPacket);
|
||||
application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
|
||||
break;
|
||||
|
||||
case PacketTypeModelAddResponse:
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
ModelItem::handleAddModelResponse(incomingPacket);
|
||||
application->getModels()->getTree()->handleAddModelResponse(incomingPacket);
|
||||
break;
|
||||
|
||||
case PacketTypeParticleData:
|
||||
case PacketTypeParticleErase:
|
||||
case PacketTypeModelData:
|
||||
|
@ -120,6 +119,18 @@ void DatagramProcessor::processDatagrams() {
|
|||
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
|
||||
break;
|
||||
}
|
||||
case PacketTypeDomainOAuthRequest: {
|
||||
QDataStream readStream(incomingPacket);
|
||||
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
|
||||
|
||||
QUrl authorizationURL;
|
||||
readStream >> authorizationURL;
|
||||
|
||||
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
|
||||
Q_ARG(const QUrl&, authorizationURL));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
nodeList->processNodeData(senderSockAddr, incomingPacket);
|
||||
break;
|
||||
|
|
|
@ -336,7 +336,6 @@ Menu::Menu() :
|
|||
SLOT(setFilter(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true);
|
||||
|
@ -1100,7 +1099,7 @@ void Menu::toggleLoginMenuItem() {
|
|||
|
||||
if (accountManager.isLoggedIn()) {
|
||||
// change the menu item to logout
|
||||
_loginAction->setText("Logout " + accountManager.getUsername());
|
||||
_loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername());
|
||||
connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
|
||||
} else {
|
||||
// change the menu item to login
|
||||
|
|
|
@ -340,7 +340,6 @@ namespace MenuOption {
|
|||
const QString Particles = "Particles";
|
||||
const QString PasteToVoxel = "Paste to Voxel...";
|
||||
const QString PipelineWarnings = "Show Render Pipeline Warnings";
|
||||
const QString PlaySlaps = "Play Slaps";
|
||||
const QString Preferences = "Preferences...";
|
||||
const QString Quit = "Quit";
|
||||
const QString ReloadAllScripts = "Reload All Scripts";
|
||||
|
|
|
@ -153,14 +153,14 @@ bool ModelUploader::zip() {
|
|||
|
||||
// mixamo/autodesk defaults
|
||||
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();
|
||||
if (!joints.contains("jointEyeLeft")) {
|
||||
joints.insert("jointEyeLeft", "LeftEye");
|
||||
joints.insert("jointEyeLeft", geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye");
|
||||
}
|
||||
if (!joints.contains("jointEyeRight")) {
|
||||
joints.insert("jointEyeRight", "RightEye");
|
||||
joints.insert("jointEyeRight", geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye");
|
||||
}
|
||||
if (!joints.contains("jointNeck")) {
|
||||
joints.insert("jointNeck", "Neck");
|
||||
|
@ -172,7 +172,8 @@ bool ModelUploader::zip() {
|
|||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
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")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
|
@ -600,7 +601,7 @@ static void setJointText(QComboBox* box, const QString& text) {
|
|||
void ModelPropertiesDialog::reset() {
|
||||
_name->setText(_originalMapping.value(NAME_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();
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
|
|
|
@ -34,12 +34,13 @@ XmppClient& XmppClient::getInstance() {
|
|||
|
||||
void XmppClient::xmppConnected() {
|
||||
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getUsername());
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
|
||||
_publicChatRoom->join();
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -50,8 +51,8 @@ void XmppClient::connectToServer() {
|
|||
connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error)));
|
||||
}
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
QString user = accountManager.getUsername();
|
||||
const QString& password = accountManager.getXMPPPassword();
|
||||
QString user = accountManager.getAccountInfo().getUsername();
|
||||
const QString& password = accountManager.getAccountInfo().getXMPPPassword();
|
||||
_xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -776,7 +776,16 @@ float Avatar::getSkeletonHeight() const {
|
|||
|
||||
float Avatar::getHeadHeight() const {
|
||||
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 {
|
||||
|
|
|
@ -29,11 +29,11 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
neckPosition = owningAvatar->getPosition();
|
||||
}
|
||||
setTranslation(neckPosition);
|
||||
glm::quat neckRotation;
|
||||
if (!owningAvatar->getSkeletonModel().getNeckRotation(neckRotation)) {
|
||||
neckRotation = owningAvatar->getOrientation();
|
||||
glm::quat neckParentRotation;
|
||||
if (!owningAvatar->getSkeletonModel().getNeckParentRotation(neckParentRotation)) {
|
||||
neckParentRotation = owningAvatar->getOrientation();
|
||||
}
|
||||
setRotation(neckRotation);
|
||||
setRotation(neckParentRotation);
|
||||
const float MODEL_SCALE = 0.0006f;
|
||||
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);
|
||||
|
||||
|
|
|
@ -26,8 +26,6 @@ public:
|
|||
|
||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
|
||||
|
|
|
@ -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.
|
||||
const float MAX_COLLISIONS_PER_AVATAR = 32;
|
||||
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) {
|
||||
if (!avatar || avatar == _owningAvatar) {
|
||||
// don't collide hands against ourself (that is done elsewhere)
|
||||
|
|
|
@ -58,7 +58,6 @@ public:
|
|||
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [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 collideAgainstOurself();
|
||||
|
||||
|
@ -80,8 +79,6 @@ private:
|
|||
void renderLeapFingerTrails();
|
||||
|
||||
void calculateGeometry();
|
||||
|
||||
void playSlaps(PalmData& palm, Avatar* avatar);
|
||||
};
|
||||
|
||||
#endif // hifi_Hand_h
|
||||
|
|
|
@ -161,7 +161,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
_leftEyePosition = _rightEyePosition = getPosition();
|
||||
if (!billboard) {
|
||||
_faceModel.simulate(deltaTime);
|
||||
_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) {
|
||||
static_cast<Avatar*>(_owningAvatar)->getSkeletonModel().getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
}
|
||||
}
|
||||
_eyePosition = calculateAverageEyePosition();
|
||||
}
|
||||
|
|
|
@ -221,6 +221,14 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const
|
|||
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) {
|
||||
if (jointIndex == -1) {
|
||||
return;
|
||||
|
|
|
@ -46,6 +46,8 @@ protected:
|
|||
virtual void updateJointState(int index);
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -434,6 +434,17 @@ bool Model::getNeckRotation(glm::quat& neckRotation) const {
|
|||
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 {
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
|
|
|
@ -132,6 +132,10 @@ public:
|
|||
/// \return whether or not the neck was found
|
||||
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.
|
||||
/// \return whether or not both eye meshes were found
|
||||
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||
|
|
|
@ -254,7 +254,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
|
|||
}
|
||||
|
||||
// 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
|
||||
ChatMessageArea* messageArea = new ChatMessageArea(true);
|
||||
|
|
116
interface/src/ui/OAuthWebViewHandler.cpp
Normal file
116
interface/src/ui/OAuthWebViewHandler.cpp
Normal 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();
|
||||
}
|
41
interface/src/ui/OAuthWebViewHandler.h
Normal file
41
interface/src/ui/OAuthWebViewHandler.h
Normal 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
|
|
@ -86,7 +86,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
|||
// replace decimal . with '-'
|
||||
formattedLocation.replace('.', '-');
|
||||
|
||||
QString username = AccountManager::getInstance().getUsername();
|
||||
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
// normalize username, replace all non alphanumeric with '-'
|
||||
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
||||
|
||||
|
|
|
@ -33,9 +33,9 @@ void Sphere3DOverlay::render() {
|
|||
|
||||
glDisable(GL_LIGHTING);
|
||||
glPushMatrix();
|
||||
glTranslatef(_position.x + _size * 0.5f,
|
||||
_position.y + _size * 0.5f,
|
||||
_position.z + _size * 0.5f);
|
||||
glTranslatef(_position.x,
|
||||
_position.y,
|
||||
_position.z);
|
||||
glLineWidth(_lineWidth);
|
||||
const int slices = 15;
|
||||
if (_isSolid) {
|
||||
|
|
|
@ -28,8 +28,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
_parentManager(parentManager),
|
||||
_socket(socket),
|
||||
_stream(socket),
|
||||
_address(socket->peerAddress()) {
|
||||
|
||||
_address(socket->peerAddress())
|
||||
{
|
||||
// take over ownership of the socket
|
||||
_socket->setParent(this);
|
||||
|
||||
|
@ -42,7 +42,7 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
HTTPConnection::~HTTPConnection() {
|
||||
// log the destruction
|
||||
if (_socket->error() != QAbstractSocket::UnknownSocketError) {
|
||||
qDebug() << _socket->errorString();
|
||||
qDebug() << _socket->errorString() << "-" << _socket->error();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
|
||||
#include <QDataStream>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QIODevice>
|
||||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QUrl>
|
||||
|
|
|
@ -18,116 +18,6 @@
|
|||
#include "HTTPConnection.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) :
|
||||
QTcpServer(parent),
|
||||
_documentRoot(documentRoot),
|
||||
|
@ -138,14 +28,131 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH
|
|||
qDebug() << "Failed to open HTTP server socket:" << errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
// connect the connection signal
|
||||
connect(this, SIGNAL(newConnection()), SLOT(acceptConnections()));
|
||||
}
|
||||
|
||||
void HTTPManager::acceptConnections() {
|
||||
QTcpSocket* socket;
|
||||
while ((socket = nextPendingConnection()) != 0) {
|
||||
void HTTPManager::incomingConnection(qintptr socketDescriptor) {
|
||||
QTcpSocket* socket = new QTcpSocket(this);
|
||||
|
||||
if (socket->setSocketDescriptor(socketDescriptor)) {
|
||||
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);
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
#include <QtNetwork/QTcpServer>
|
||||
|
||||
class HTTPConnection;
|
||||
class HTTPSConnection;
|
||||
|
||||
class HTTPRequestHandler {
|
||||
public:
|
||||
|
@ -35,9 +36,10 @@ public:
|
|||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
protected slots:
|
||||
protected:
|
||||
/// Accepts all pending connections
|
||||
void acceptConnections();
|
||||
virtual void incomingConnection(qintptr socketDescriptor);
|
||||
virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
|
||||
protected:
|
||||
QString _documentRoot;
|
||||
HTTPRequestHandler* _requestHandler;
|
||||
|
|
23
libraries/embedded-webserver/src/HTTPSConnection.cpp
Normal file
23
libraries/embedded-webserver/src/HTTPSConnection.cpp
Normal 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;
|
||||
}
|
26
libraries/embedded-webserver/src/HTTPSConnection.h
Normal file
26
libraries/embedded-webserver/src/HTTPSConnection.h
Normal 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
|
51
libraries/embedded-webserver/src/HTTPSManager.cpp
Normal file
51
libraries/embedded-webserver/src/HTTPSManager.cpp
Normal 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);
|
||||
}
|
50
libraries/embedded-webserver/src/HTTPSManager.h
Normal file
50
libraries/embedded-webserver/src/HTTPSManager.h
Normal 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
|
|
@ -406,7 +406,7 @@ QVariantHash parseMapping(QIODevice* device) {
|
|||
|
||||
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
|
||||
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 y = *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> 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 t = *it++;
|
||||
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));
|
||||
}
|
||||
|
||||
QVector<int> getIntVector(const QVariantList& properties, int index) {
|
||||
if (index >= properties.size()) {
|
||||
QVector<int> getIntVector(const FBXNode& node) {
|
||||
foreach (const FBXNode& child, node.children) {
|
||||
if (child.name == "a") {
|
||||
return getIntVector(child);
|
||||
}
|
||||
}
|
||||
if (node.properties.isEmpty()) {
|
||||
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()) {
|
||||
return vector;
|
||||
}
|
||||
for (; index < properties.size(); index++) {
|
||||
vector.append(properties.at(index).toInt());
|
||||
for (int i = 0; i < node.properties.size(); i++) {
|
||||
vector.append(node.properties.at(i).toInt());
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
QVector<qlonglong> getLongVector(const QVariantList& properties, int index) {
|
||||
if (index >= properties.size()) {
|
||||
return QVector<qlonglong>();
|
||||
QVector<float> getFloatVector(const FBXNode& node) {
|
||||
foreach (const FBXNode& child, node.children) {
|
||||
if (child.name == "a") {
|
||||
return getFloatVector(child);
|
||||
}
|
||||
}
|
||||
QVector<qlonglong> vector = properties.at(index).value<QVector<qlonglong> >();
|
||||
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()) {
|
||||
if (node.properties.isEmpty()) {
|
||||
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()) {
|
||||
return vector;
|
||||
}
|
||||
for (; index < properties.size(); index++) {
|
||||
vector.append(properties.at(index).toFloat());
|
||||
for (int i = 0; i < node.properties.size(); i++) {
|
||||
vector.append(node.properties.at(i).toFloat());
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
QVector<double> getDoubleVector(const QVariantList& properties, int index) {
|
||||
if (index >= properties.size()) {
|
||||
QVector<double> getDoubleVector(const FBXNode& node) {
|
||||
foreach (const FBXNode& child, node.children) {
|
||||
if (child.name == "a") {
|
||||
return getDoubleVector(child);
|
||||
}
|
||||
}
|
||||
if (node.properties.isEmpty()) {
|
||||
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()) {
|
||||
return vector;
|
||||
}
|
||||
for (; index < properties.size(); index++) {
|
||||
vector.append(properties.at(index).toDouble());
|
||||
for (int i = 0; i < node.properties.size(); i++) {
|
||||
vector.append(node.properties.at(i).toDouble());
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
@ -697,21 +698,30 @@ public:
|
|||
};
|
||||
|
||||
void appendIndex(MeshData& data, QVector<int>& indices, int index) {
|
||||
if (index >= data.polygonIndices.size()) {
|
||||
return;
|
||||
}
|
||||
int vertexIndex = data.polygonIndices.at(index);
|
||||
if (vertexIndex < 0) {
|
||||
vertexIndex = -vertexIndex - 1;
|
||||
}
|
||||
|
||||
Vertex vertex;
|
||||
vertex.originalIndex = vertexIndex;
|
||||
|
||||
glm::vec3 position;
|
||||
if (vertexIndex < data.vertices.size()) {
|
||||
position = data.vertices.at(vertexIndex);
|
||||
}
|
||||
|
||||
glm::vec3 normal;
|
||||
if (data.normalIndices.isEmpty()) {
|
||||
normal = data.normals.at(data.normalsByVertex ? vertexIndex : index);
|
||||
|
||||
} else {
|
||||
int normalIndex = data.normalIndices.at(data.normalsByVertex ? vertexIndex : index);
|
||||
if (normalIndex >= 0) {
|
||||
int normalIndex = data.normalsByVertex ? vertexIndex : index;
|
||||
if (data.normalIndices.isEmpty()) {
|
||||
if (normalIndex < data.normals.size()) {
|
||||
normal = data.normals.at(normalIndex);
|
||||
}
|
||||
} else if (normalIndex < data.normalIndices.size()) {
|
||||
normalIndex = data.normalIndices.at(normalIndex);
|
||||
if (normalIndex >= 0 && normalIndex < data.normals.size()) {
|
||||
normal = data.normals.at(normalIndex);
|
||||
}
|
||||
}
|
||||
|
@ -720,9 +730,9 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
|
|||
if (index < data.texCoords.size()) {
|
||||
vertex.texCoord = data.texCoords.at(index);
|
||||
}
|
||||
} else {
|
||||
} else if (index < data.texCoordIndices.size()) {
|
||||
int texCoordIndex = data.texCoordIndices.at(index);
|
||||
if (texCoordIndex >= 0) {
|
||||
if (texCoordIndex >= 0 && texCoordIndex < data.texCoords.size()) {
|
||||
vertex.texCoord = data.texCoords.at(texCoordIndex);
|
||||
}
|
||||
}
|
||||
|
@ -733,7 +743,7 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index) {
|
|||
indices.append(newIndex);
|
||||
data.indices.insert(vertex, 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.texCoords.append(vertex.texCoord);
|
||||
|
||||
|
@ -749,44 +759,51 @@ ExtractedMesh extractMesh(const FBXNode& object) {
|
|||
QVector<int> textures;
|
||||
foreach (const FBXNode& child, object.children) {
|
||||
if (child.name == "Vertices") {
|
||||
data.vertices = createVec3Vector(getDoubleVector(child.properties, 0));
|
||||
data.vertices = createVec3Vector(getDoubleVector(child));
|
||||
|
||||
} else if (child.name == "PolygonVertexIndex") {
|
||||
data.polygonIndices = getIntVector(child.properties, 0);
|
||||
data.polygonIndices = getIntVector(child);
|
||||
|
||||
} else if (child.name == "LayerElementNormal") {
|
||||
data.normalsByVertex = false;
|
||||
bool indexToDirect = false;
|
||||
foreach (const FBXNode& subdata, child.children) {
|
||||
if (subdata.name == "Normals") {
|
||||
data.normals = createVec3Vector(getDoubleVector(subdata.properties, 0));
|
||||
data.normals = createVec3Vector(getDoubleVector(subdata));
|
||||
|
||||
} else if (subdata.name == "NormalsIndex") {
|
||||
data.normalIndices = getIntVector(subdata.properties, 0);
|
||||
data.normalIndices = getIntVector(subdata);
|
||||
|
||||
} else if (subdata.name == "MappingInformationType" &&
|
||||
subdata.properties.at(0) == "ByVertice") {
|
||||
} else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == "ByVertice") {
|
||||
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) {
|
||||
foreach (const FBXNode& subdata, child.children) {
|
||||
if (subdata.name == "UV") {
|
||||
data.texCoords = createVec2Vector(getDoubleVector(subdata.properties, 0));
|
||||
data.texCoords = createVec2Vector(getDoubleVector(subdata));
|
||||
|
||||
} else if (subdata.name == "UVIndex") {
|
||||
data.texCoordIndices = getIntVector(subdata.properties, 0);
|
||||
data.texCoordIndices = getIntVector(subdata);
|
||||
}
|
||||
}
|
||||
} else if (child.name == "LayerElementMaterial") {
|
||||
foreach (const FBXNode& subdata, child.children) {
|
||||
if (subdata.name == "Materials") {
|
||||
materials = getIntVector(subdata.properties, 0);
|
||||
materials = getIntVector(subdata);
|
||||
}
|
||||
}
|
||||
} else if (child.name == "LayerElementTexture") {
|
||||
foreach (const FBXNode& subdata, child.children) {
|
||||
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;
|
||||
for (int beginIndex = 0; beginIndex < data.polygonIndices.size(); polygonIndex++) {
|
||||
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,
|
||||
(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, nextIndex++);
|
||||
appendIndex(data, part.triangleIndices, nextIndex);
|
||||
if (data.polygonIndices.at(nextIndex) < 0) {
|
||||
if (nextIndex >= data.polygonIndices.size() || data.polygonIndices.at(nextIndex) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -835,13 +852,13 @@ FBXBlendshape extractBlendshape(const FBXNode& object) {
|
|||
FBXBlendshape blendshape;
|
||||
foreach (const FBXNode& data, object.children) {
|
||||
if (data.name == "Indexes") {
|
||||
blendshape.indices = getIntVector(data.properties, 0);
|
||||
blendshape.indices = getIntVector(data);
|
||||
|
||||
} else if (data.name == "Vertices") {
|
||||
blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0));
|
||||
blendshape.vertices = createVec3Vector(getDoubleVector(data));
|
||||
|
||||
} else if (data.name == "Normals") {
|
||||
blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0));
|
||||
blendshape.normals = createVec3Vector(getDoubleVector(data));
|
||||
}
|
||||
}
|
||||
return blendshape;
|
||||
|
@ -1016,7 +1033,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
foreach (const FBXNode& object, child.children) {
|
||||
if (object.name == "SceneInfo") {
|
||||
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) {
|
||||
if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 &&
|
||||
subsubobject.properties.at(0) == "Original|ApplicationName") {
|
||||
|
@ -1262,13 +1285,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
Cluster cluster;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "Indexes") {
|
||||
cluster.indices = getIntVector(subobject.properties, 0);
|
||||
cluster.indices = getIntVector(subobject);
|
||||
|
||||
} else if (subobject.name == "Weights") {
|
||||
cluster.weights = getDoubleVector(subobject.properties, 0);
|
||||
cluster.weights = getDoubleVector(subobject);
|
||||
|
||||
} else if (subobject.name == "TransformLink") {
|
||||
QVector<double> values = getDoubleVector(subobject.properties, 0);
|
||||
QVector<double> values = getDoubleVector(subobject);
|
||||
cluster.transformLink = createMat4(values);
|
||||
}
|
||||
}
|
||||
|
@ -1290,7 +1313,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
AnimationCurve curve;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "KeyValueFloat") {
|
||||
curve.values = getFloatVector(subobject.properties, 0);
|
||||
curve.values = getFloatVector(subobject);
|
||||
}
|
||||
}
|
||||
animationCurves.insert(getID(object.properties), curve);
|
||||
|
|
|
@ -47,6 +47,9 @@ public:
|
|||
/// \return true if point is within current limits
|
||||
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 maximum;
|
||||
};
|
||||
|
@ -174,6 +177,7 @@ public:
|
|||
class FBXGeometry {
|
||||
public:
|
||||
|
||||
QString author;
|
||||
QString applicationName; ///< the name of the application that generated the model
|
||||
|
||||
QVector<FBXJoint> joints;
|
||||
|
|
|
@ -55,9 +55,7 @@ public:
|
|||
|
||||
void requestAccessToken(const QString& login, const QString& password);
|
||||
|
||||
QString getUsername() const { return _accountInfo.getUsername(); }
|
||||
|
||||
const QString& getXMPPPassword() const { return _accountInfo.getXMPPPassword(); }
|
||||
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
|
||||
|
||||
void destroy() { delete _networkAccessManager; }
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ Assignment::Assignment() :
|
|||
_type(Assignment::AllTypes),
|
||||
_pool(),
|
||||
_location(Assignment::LocalLocation),
|
||||
_payload()
|
||||
_payload(),
|
||||
_isStatic(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -61,7 +62,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
|
|||
_type(type),
|
||||
_pool(pool),
|
||||
_location(location),
|
||||
_payload()
|
||||
_payload(),
|
||||
_isStatic(false)
|
||||
{
|
||||
if (_command == Assignment::CreateCommand) {
|
||||
// this is a newly created assignment, generate a random UUID
|
||||
|
|
|
@ -78,6 +78,9 @@ public:
|
|||
void setPool(const QString& pool) { _pool = pool; };
|
||||
const QString& getPool() const { return _pool; }
|
||||
|
||||
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
|
||||
bool isStatic() const { return _isStatic; }
|
||||
|
||||
const char* getTypeName() const;
|
||||
|
||||
// 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
|
||||
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
|
||||
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
|
||||
};
|
||||
|
||||
#endif // hifi_Assignment_h
|
||||
|
|
|
@ -78,7 +78,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() {
|
|||
static bool datumInitialized = false;
|
||||
|
||||
static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] =
|
||||
"-----BEGIN CERTIFICATE-----"
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
|
||||
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
|
||||
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
|
||||
|
@ -100,7 +100,7 @@ gnutls_datum_t* DTLSSession::highFidelityCADatum() {
|
|||
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
|
||||
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
|
||||
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
|
||||
"-----END CERTIFICATE-----";
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
if (!datumInitialized) {
|
||||
hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT;
|
||||
|
|
|
@ -22,8 +22,11 @@
|
|||
#include "HifiSockAddr.h"
|
||||
|
||||
const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io";
|
||||
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
|
||||
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 {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -106,6 +106,10 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() {
|
|||
|
||||
_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)
|
||||
qDebug() << "Making required DTLS changes to LimitedNodeList DTLS socket.";
|
||||
|
||||
|
|
|
@ -378,12 +378,17 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
}
|
||||
}
|
||||
|
||||
PacketType domainPacketType = _sessionUUID.isNull()
|
||||
PacketType domainPacketType = !_domainHandler.isConnected()
|
||||
? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
|
||||
|
||||
// construct the DS check in packet
|
||||
QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest
|
||||
? _sessionUUID : _domainHandler.getAssignmentUUID());
|
||||
QUuid packetUUID = _sessionUUID;
|
||||
|
||||
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);
|
||||
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
||||
|
@ -404,7 +409,6 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
dtlsSession->writeDatagram(domainServerPacket);
|
||||
}
|
||||
|
||||
|
||||
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
|
||||
static unsigned int numDomainCheckins = 0;
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ enum PacketType {
|
|||
PacketTypeDomainListRequest,
|
||||
PacketTypeRequestAssignment,
|
||||
PacketTypeCreateAssignment,
|
||||
PacketTypeDataServerPut, // reusable
|
||||
PacketTypeDomainOAuthRequest,
|
||||
PacketTypeDataServerGet, // reusable
|
||||
PacketTypeDataServerSend, // reusable
|
||||
PacketTypeDataServerConfirm,
|
||||
|
@ -72,7 +72,7 @@ typedef char PacketVersion;
|
|||
|
||||
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||
<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest
|
||||
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
|
||||
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
|
||||
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery;
|
||||
|
||||
|
|
|
@ -17,6 +17,10 @@ glm::vec3 Vec3::cross(const glm::vec3& v1, const glm::vec3& 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) {
|
||||
return v1 * f;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ class Vec3 : public QObject {
|
|||
|
||||
public slots:
|
||||
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 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
|
||||
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
|
||||
|
|
Loading…
Reference in a new issue