mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-28 16:30:42 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into attribution_property
This commit is contained in:
commit
0a3d936427
28 changed files with 545 additions and 236 deletions
|
@ -21,6 +21,9 @@ if (POLICY CMP0042)
|
||||||
cmake_policy(SET CMP0042 OLD)
|
cmake_policy(SET CMP0042 OLD)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
|
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets")
|
||||||
|
|
||||||
project(hifi)
|
project(hifi)
|
||||||
add_definitions(-DGLM_FORCE_RADIANS)
|
add_definitions(-DGLM_FORCE_RADIANS)
|
||||||
|
|
||||||
|
@ -173,9 +176,13 @@ endif ()
|
||||||
# add subdirectories for all targets
|
# add subdirectories for all targets
|
||||||
if (NOT ANDROID)
|
if (NOT ANDROID)
|
||||||
add_subdirectory(assignment-client)
|
add_subdirectory(assignment-client)
|
||||||
|
set_target_properties(assignment-client PROPERTIES FOLDER "Apps")
|
||||||
add_subdirectory(domain-server)
|
add_subdirectory(domain-server)
|
||||||
|
set_target_properties(domain-server PROPERTIES FOLDER "Apps")
|
||||||
add_subdirectory(ice-server)
|
add_subdirectory(ice-server)
|
||||||
|
set_target_properties(ice-server PROPERTIES FOLDER "Apps")
|
||||||
add_subdirectory(interface)
|
add_subdirectory(interface)
|
||||||
|
set_target_properties(interface PROPERTIES FOLDER "Apps")
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
add_subdirectory(tools)
|
add_subdirectory(tools)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
#include <PacketHeaders.h>
|
#include <PacketHeaders.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
|
#include <TryLocker.h>
|
||||||
|
|
||||||
#include "AvatarMixerClientData.h"
|
#include "AvatarMixerClientData.h"
|
||||||
|
|
||||||
#include "AvatarMixer.h"
|
#include "AvatarMixer.h"
|
||||||
|
|
||||||
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||||
|
@ -119,12 +119,25 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
AvatarMixerClientData* nodeData = NULL;
|
nodeList->eachMatchingNode(
|
||||||
AvatarMixerClientData* otherNodeData = NULL;
|
[&](const SharedNodePointer& node)->bool {
|
||||||
|
if (!node->getLinkedData()) {
|
||||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
return false;
|
||||||
if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()
|
}
|
||||||
&& (nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData()))->getMutex().tryLock()) {
|
if (node->getType() != NodeType::Agent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!node->getActiveSocket()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[&](const SharedNodePointer& node) {
|
||||||
|
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||||
|
MutexTryLocker lock(nodeData->getMutex());
|
||||||
|
if (!lock.isLocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
++_sumListeners;
|
++_sumListeners;
|
||||||
|
|
||||||
// reset packet pointers for this node
|
// reset packet pointers for this node
|
||||||
|
@ -132,25 +145,47 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
|
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
AvatarData& avatar = nodeData->getAvatar();
|
||||||
glm::vec3 myPosition = avatar.getPosition();
|
glm::vec3 myPosition = avatar.getPosition();
|
||||||
|
// TODO use this along with the distance in the calculation of whether to send an update
|
||||||
|
// about a given otherNode to this node
|
||||||
|
// FIXME does this mean we should sort the othernodes by distance before iterating
|
||||||
|
// over them?
|
||||||
|
float outputBandwidth = node->getOutboundBandwidth();
|
||||||
|
|
||||||
// this is an AGENT we have received head data from
|
// this is an AGENT we have received head data from
|
||||||
// send back a packet with other active node data to this node
|
// send back a packet with other active node data to this node
|
||||||
nodeList->eachNode([&](const SharedNodePointer& otherNode) {
|
nodeList->eachMatchingNode(
|
||||||
if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()
|
[&](const SharedNodePointer& otherNode)->bool {
|
||||||
&& (otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData()))->getMutex().tryLock()) {
|
if (!otherNode->getLinkedData()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (otherNode->getUUID() == node->getUUID()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
// Check throttling value
|
||||||
|
if (!(_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[&](const SharedNodePointer& otherNode) {
|
||||||
|
AvatarMixerClientData* otherNodeData = otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||||
|
MutexTryLocker lock(otherNodeData->getMutex());
|
||||||
|
if (!lock.isLocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
AvatarData& otherAvatar = otherNodeData->getAvatar();
|
AvatarData& otherAvatar = otherNodeData->getAvatar();
|
||||||
glm::vec3 otherPosition = otherAvatar.getPosition();
|
// Decide whether to send this avatar's data based on it's distance from us
|
||||||
|
|
||||||
float distanceToAvatar = glm::length(myPosition - otherPosition);
|
|
||||||
// The full rate distance is the distance at which EVERY update will be sent for this avatar
|
// The full rate distance is the distance at which EVERY update will be sent for this avatar
|
||||||
// at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
// at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
||||||
const float FULL_RATE_DISTANCE = 2.0f;
|
const float FULL_RATE_DISTANCE = 2.0f;
|
||||||
|
glm::vec3 otherPosition = otherAvatar.getPosition();
|
||||||
|
float distanceToAvatar = glm::length(myPosition - otherPosition);
|
||||||
|
|
||||||
|
if (!(distanceToAvatar == 0.0f || randFloat() < FULL_RATE_DISTANCE / distanceToAvatar)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Decide whether to send this avatar's data based on it's distance from us
|
|
||||||
if ((_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio))
|
|
||||||
&& (distanceToAvatar == 0.0f || randFloat() < FULL_RATE_DISTANCE / distanceToAvatar)) {
|
|
||||||
QByteArray avatarByteArray;
|
QByteArray avatarByteArray;
|
||||||
avatarByteArray.append(otherNode->getUUID().toRfc4122());
|
avatarByteArray.append(otherNode->getUUID().toRfc4122());
|
||||||
avatarByteArray.append(otherAvatar.toByteArray());
|
avatarByteArray.append(otherAvatar.toByteArray());
|
||||||
|
@ -199,16 +234,8 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
|
|
||||||
++_sumIdentityPackets;
|
++_sumIdentityPackets;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
otherNodeData->getMutex().unlock();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||||
|
|
||||||
nodeData->getMutex().unlock();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
|
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
|
|
@ -16,6 +16,7 @@ macro(LINK_HIFI_LIBRARIES)
|
||||||
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
|
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
|
||||||
if (NOT TARGET ${HIFI_LIBRARY})
|
if (NOT TARGET ${HIFI_LIBRARY})
|
||||||
add_subdirectory("${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}" "${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}")
|
add_subdirectory("${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}" "${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}")
|
||||||
|
set_target_properties(${HIFI_LIBRARY} PROPERTIES FOLDER "Libraries")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
|
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
|
||||||
|
|
|
@ -613,12 +613,14 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
||||||
|
|
||||||
packetStream >> nodeInterestList >> username >> usernameSignature;
|
packetStream >> nodeInterestList >> username >> usernameSignature;
|
||||||
|
|
||||||
if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr)) {
|
QString reason;
|
||||||
|
if (!isAssignment && !shouldAllowConnectionFromNode(username, usernameSignature, senderSockAddr, reason)) {
|
||||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||||
QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied);
|
QByteArray connectionDeniedByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied);
|
||||||
|
QDataStream out(&connectionDeniedByteArray, QIODevice::WriteOnly | QIODevice::Append);
|
||||||
// send this oauth request datagram back to the client
|
out << reason;
|
||||||
DependencyManager::get<LimitedNodeList>()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr);
|
// tell client it has been refused.
|
||||||
|
DependencyManager::get<LimitedNodeList>()->writeUnverifiedDatagram(connectionDeniedByteArray, senderSockAddr);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -680,34 +682,9 @@ unsigned int DomainServer::countConnectedUsers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
bool DomainServer::verifyUsersKey (const QString& username,
|
||||||
const QByteArray& usernameSignature,
|
const QByteArray& usernameSignature,
|
||||||
const HifiSockAddr& senderSockAddr) {
|
QString& reasonReturn) {
|
||||||
|
|
||||||
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
|
|
||||||
ALLOWED_USERS_SETTINGS_KEYPATH);
|
|
||||||
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
|
||||||
|
|
||||||
// we always let in a user who is sending a packet from our local socket or from the localhost address
|
|
||||||
if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress()
|
|
||||||
|| senderSockAddr.getAddress() == QHostAddress::LocalHost) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
|
||||||
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
|
||||||
if (maximumUserCapacity > 0) {
|
|
||||||
unsigned int connectedUsers = countConnectedUsers();
|
|
||||||
if (connectedUsers >= maximumUserCapacity) {
|
|
||||||
// too many users, deny the new connection.
|
|
||||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowedUsers.count() > 0) {
|
|
||||||
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
|
||||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||||
|
|
||||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||||
|
@ -721,7 +698,8 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||||
|
|
||||||
if (rsaPublicKey) {
|
if (rsaPublicKey) {
|
||||||
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
||||||
int decryptResult = RSA_public_decrypt(usernameSignature.size(),
|
int decryptResult =
|
||||||
|
RSA_public_decrypt(usernameSignature.size(),
|
||||||
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
reinterpret_cast<const unsigned char*>(usernameSignature.constData()),
|
||||||
reinterpret_cast<unsigned char*>(decryptedArray.data()),
|
reinterpret_cast<unsigned char*>(decryptedArray.data()),
|
||||||
rsaPublicKey, RSA_PKCS1_PADDING);
|
rsaPublicKey, RSA_PKCS1_PADDING);
|
||||||
|
@ -736,9 +714,11 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Username signature did not match for" << username << "- denying connection.";
|
qDebug() << "Username signature did not match for" << username << "- denying connection.";
|
||||||
|
reasonReturn = "Username signature did not match.";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection.";
|
qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection.";
|
||||||
|
reasonReturn = "Couldn't decrypt user signature.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// free up the public key, we don't need it anymore
|
// free up the public key, we don't need it anymore
|
||||||
|
@ -746,19 +726,69 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||||
} else {
|
} else {
|
||||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||||
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
|
||||||
|
reasonReturn = "Couldn't convert data to RSA key.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUserPublicKey(username);
|
requestUserPublicKey(username); // no joy. maybe next time?
|
||||||
} else {
|
return false;
|
||||||
qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// since we have no allowed user list, let them all in
|
|
||||||
|
bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
|
||||||
|
const QByteArray& usernameSignature,
|
||||||
|
const HifiSockAddr& senderSockAddr,
|
||||||
|
QString& reasonReturn) {
|
||||||
|
|
||||||
|
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
|
||||||
|
ALLOWED_USERS_SETTINGS_KEYPATH);
|
||||||
|
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
|
||||||
|
|
||||||
|
// we always let in a user who is sending a packet from our local socket or from the localhost address
|
||||||
|
if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress()
|
||||||
|
|| senderSockAddr.getAddress() == QHostAddress::LocalHost) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowedUsers.count() > 0) {
|
||||||
|
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
|
||||||
|
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
|
||||||
|
reasonReturn = "User not on whitelist.";
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
// we have no allowed user list.
|
||||||
|
|
||||||
|
// if this user is in the editors list, exempt them from the max-capacity check
|
||||||
|
const QVariant* allowedEditorsVariant =
|
||||||
|
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
|
||||||
|
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
|
||||||
|
if (allowedEditors.contains(username)) {
|
||||||
|
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we haven't reached max-capacity, let them in.
|
||||||
|
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
|
||||||
|
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
|
||||||
|
if (maximumUserCapacity > 0) {
|
||||||
|
unsigned int connectedUsers = countConnectedUsers();
|
||||||
|
if (connectedUsers >= maximumUserCapacity) {
|
||||||
|
// too many users, deny the new connection.
|
||||||
|
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
|
||||||
|
reasonReturn = "Too many connected users.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::preloadAllowedUserPublicKeys() {
|
void DomainServer::preloadAllowedUserPublicKeys() {
|
||||||
|
|
|
@ -84,8 +84,9 @@ private:
|
||||||
|
|
||||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||||
unsigned int countConnectedUsers();
|
unsigned int countConnectedUsers();
|
||||||
|
bool verifyUsersKey (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn);
|
||||||
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
|
||||||
const HifiSockAddr& senderSockAddr);
|
const HifiSockAddr& senderSockAddr, QString& reasonReturn);
|
||||||
|
|
||||||
void preloadAllowedUserPublicKeys();
|
void preloadAllowedUserPublicKeys();
|
||||||
void requestUserPublicKey(const QString& username);
|
void requestUserPublicKey(const QString& username);
|
||||||
|
|
|
@ -873,8 +873,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::importSVOFromURL(QUrl url) {
|
bool Application::importSVOFromURL(const QString& urlString) {
|
||||||
|
QUrl url(urlString);
|
||||||
emit svoImportRequested(url.url());
|
emit svoImportRequested(url.url());
|
||||||
|
return true; // assume it's accepted
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::event(QEvent* event) {
|
bool Application::event(QEvent* event) {
|
||||||
|
@ -887,12 +889,9 @@ bool Application::event(QEvent* event) {
|
||||||
QUrl url = fileEvent->url();
|
QUrl url = fileEvent->url();
|
||||||
|
|
||||||
if (!url.isEmpty()) {
|
if (!url.isEmpty()) {
|
||||||
if (url.scheme() == HIFI_URL_SCHEME) {
|
QString urlString = url.toString();
|
||||||
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
|
if (canAcceptURL(urlString)) {
|
||||||
} else if (url.path().toLower().endsWith(SVO_EXTENSION)) {
|
return acceptURL(urlString);
|
||||||
emit svoImportRequested(url.url());
|
|
||||||
} else if (url.path().toLower().endsWith(JS_EXTENSION)) {
|
|
||||||
askToLoadScript(url.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1453,14 +1452,26 @@ void Application::wheelEvent(QWheelEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::dropEvent(QDropEvent *event) {
|
void Application::dropEvent(QDropEvent *event) {
|
||||||
QString snapshotPath;
|
|
||||||
const QMimeData *mimeData = event->mimeData();
|
const QMimeData *mimeData = event->mimeData();
|
||||||
bool atLeastOneFileAccepted = false;
|
bool atLeastOneFileAccepted = false;
|
||||||
foreach (QUrl url, mimeData->urls()) {
|
foreach (QUrl url, mimeData->urls()) {
|
||||||
auto lower = url.path().toLower();
|
QString urlString = url.toString();
|
||||||
if (lower.endsWith(SNAPSHOT_EXTENSION)) {
|
if (canAcceptURL(urlString)) {
|
||||||
snapshotPath = url.toLocalFile();
|
if (acceptURL(urlString)) {
|
||||||
|
atLeastOneFileAccepted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atLeastOneFileAccepted) {
|
||||||
|
event->acceptProposedAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::acceptSnapshot(const QString& urlString) {
|
||||||
|
QUrl url(urlString);
|
||||||
|
QString snapshotPath = url.toLocalFile();
|
||||||
|
|
||||||
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
|
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
|
||||||
if (snapshotData) {
|
if (snapshotData) {
|
||||||
|
@ -1470,8 +1481,6 @@ void Application::dropEvent(QDropEvent *event) {
|
||||||
|
|
||||||
_myAvatar->setPosition(snapshotData->getLocation());
|
_myAvatar->setPosition(snapshotData->getLocation());
|
||||||
_myAvatar->setOrientation(snapshotData->getOrientation());
|
_myAvatar->setOrientation(snapshotData->getOrientation());
|
||||||
atLeastOneFileAccepted = true;
|
|
||||||
break; // don't process further files
|
|
||||||
} else {
|
} else {
|
||||||
QMessageBox msgBox;
|
QMessageBox msgBox;
|
||||||
msgBox.setText("No location details were found in the file "
|
msgBox.setText("No location details were found in the file "
|
||||||
|
@ -1480,19 +1489,7 @@ void Application::dropEvent(QDropEvent *event) {
|
||||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
}
|
}
|
||||||
} else if (lower.endsWith(SVO_EXTENSION)) {
|
return true;
|
||||||
emit svoImportRequested(url.url());
|
|
||||||
event->acceptProposedAction();
|
|
||||||
atLeastOneFileAccepted = true;
|
|
||||||
} else if (lower.endsWith(JS_EXTENSION)) {
|
|
||||||
askToLoadScript(url.url());
|
|
||||||
atLeastOneFileAccepted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (atLeastOneFileAccepted) {
|
|
||||||
event->acceptProposedAction();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::sendPingPackets() {
|
void Application::sendPingPackets() {
|
||||||
|
@ -3598,7 +3595,109 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
workerThread->start();
|
workerThread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
void Application::initializeAcceptedFiles() {
|
||||||
|
if (_acceptedExtensions.size() == 0) {
|
||||||
|
_acceptedExtensions[SNAPSHOT_EXTENSION] = &Application::acceptSnapshot;
|
||||||
|
_acceptedExtensions[SVO_EXTENSION] = &Application::importSVOFromURL;
|
||||||
|
_acceptedExtensions[JS_EXTENSION] = &Application::askToLoadScript;
|
||||||
|
_acceptedExtensions[FST_EXTENSION] = &Application::askToSetAvatarUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::canAcceptURL(const QString& urlString) {
|
||||||
|
initializeAcceptedFiles();
|
||||||
|
|
||||||
|
QUrl url(urlString);
|
||||||
|
if (urlString.startsWith(HIFI_URL_SCHEME)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions);
|
||||||
|
QString lowerPath = url.path().toLower();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
if (lowerPath.endsWith(i.key())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::acceptURL(const QString& urlString) {
|
||||||
|
initializeAcceptedFiles();
|
||||||
|
|
||||||
|
if (urlString.startsWith(HIFI_URL_SCHEME)) {
|
||||||
|
// this is a hifi URL - have the AddressManager handle it
|
||||||
|
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
|
||||||
|
Qt::AutoConnection, Q_ARG(const QString&, urlString));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
QUrl url(urlString);
|
||||||
|
QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions);
|
||||||
|
QString lowerPath = url.path().toLower();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
if (lowerPath.endsWith(i.key())) {
|
||||||
|
AcceptURLMethod method = i.value();
|
||||||
|
(this->*method)(urlString);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Application::askToSetAvatarUrl(const QString& url) {
|
||||||
|
QUrl realUrl(url);
|
||||||
|
if (realUrl.isLocalFile()) {
|
||||||
|
QString message = "You can not use local files for avatar components.";
|
||||||
|
|
||||||
|
QMessageBox msgBox;
|
||||||
|
msgBox.setText(message);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setIcon(QMessageBox::Warning);
|
||||||
|
msgBox.exec();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString message = "Would you like to use this model for part of avatar:\n" + url;
|
||||||
|
QMessageBox msgBox;
|
||||||
|
|
||||||
|
msgBox.setIcon(QMessageBox::Question);
|
||||||
|
msgBox.setWindowTitle("Set Avatar");
|
||||||
|
msgBox.setText(message);
|
||||||
|
|
||||||
|
QPushButton* headButton = msgBox.addButton(tr("Head"), QMessageBox::ActionRole);
|
||||||
|
QPushButton* bodyButton = msgBox.addButton(tr("Body"), QMessageBox::ActionRole);
|
||||||
|
QPushButton* bodyAndHeadButton = msgBox.addButton(tr("Body + Head"), QMessageBox::ActionRole);
|
||||||
|
msgBox.addButton(QMessageBox::Cancel);
|
||||||
|
|
||||||
|
msgBox.exec();
|
||||||
|
|
||||||
|
if (msgBox.clickedButton() == headButton) {
|
||||||
|
qDebug() << "Chose to use for head: " << url;
|
||||||
|
_myAvatar->setFaceModelURL(url);
|
||||||
|
UserActivityLogger::getInstance().changedModel("head", url);
|
||||||
|
_myAvatar->sendIdentityPacket();
|
||||||
|
} else if (msgBox.clickedButton() == bodyButton) {
|
||||||
|
qDebug() << "Chose to use for body: " << url;
|
||||||
|
_myAvatar->setSkeletonModelURL(url);
|
||||||
|
UserActivityLogger::getInstance().changedModel("skeleton", url);
|
||||||
|
_myAvatar->sendIdentityPacket();
|
||||||
|
} else if (msgBox.clickedButton() == bodyAndHeadButton) {
|
||||||
|
qDebug() << "Chose to use for body + head: " << url;
|
||||||
|
_myAvatar->setFaceModelURL(QString());
|
||||||
|
_myAvatar->setSkeletonModelURL(url);
|
||||||
|
UserActivityLogger::getInstance().changedModel("skeleton", url);
|
||||||
|
_myAvatar->sendIdentityPacket();
|
||||||
|
} else {
|
||||||
|
qDebug() << "Declined to use the avatar: " << url;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
||||||
QMessageBox::StandardButton reply;
|
QMessageBox::StandardButton reply;
|
||||||
QString message = "Would you like to run this script:\n" + scriptFilenameOrURL;
|
QString message = "Would you like to run this script:\n" + scriptFilenameOrURL;
|
||||||
reply = QMessageBox::question(getWindow(), "Run Script", message, QMessageBox::Yes|QMessageBox::No);
|
reply = QMessageBox::question(getWindow(), "Run Script", message, QMessageBox::Yes|QMessageBox::No);
|
||||||
|
@ -3609,6 +3708,7 @@ void Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Declined to run the script: " << scriptFilenameOrURL;
|
qDebug() << "Declined to run the script: " << scriptFilenameOrURL;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
|
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
|
||||||
|
|
|
@ -96,6 +96,7 @@ static const float NODE_KILLED_BLUE = 0.0f;
|
||||||
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
||||||
static const QString SVO_EXTENSION = ".svo";
|
static const QString SVO_EXTENSION = ".svo";
|
||||||
static const QString JS_EXTENSION = ".js";
|
static const QString JS_EXTENSION = ".js";
|
||||||
|
static const QString FST_EXTENSION = ".fst";
|
||||||
|
|
||||||
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
|
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
|
||||||
static const float BILLBOARD_DISTANCE = 5.56f; // meters
|
static const float BILLBOARD_DISTANCE = 5.56f; // meters
|
||||||
|
@ -127,6 +128,8 @@ class Application;
|
||||||
#endif
|
#endif
|
||||||
#define qApp (static_cast<Application*>(QCoreApplication::instance()))
|
#define qApp (static_cast<Application*>(QCoreApplication::instance()))
|
||||||
|
|
||||||
|
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
||||||
|
|
||||||
class Application : public QApplication, public AbstractViewStateInterface, AbstractScriptingServicesInterface {
|
class Application : public QApplication, public AbstractViewStateInterface, AbstractScriptingServicesInterface {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -221,7 +224,7 @@ public:
|
||||||
float getFieldOfView() { return _fieldOfView.get(); }
|
float getFieldOfView() { return _fieldOfView.get(); }
|
||||||
void setFieldOfView(float fov) { _fieldOfView.set(fov); }
|
void setFieldOfView(float fov) { _fieldOfView.set(fov); }
|
||||||
|
|
||||||
void importSVOFromURL(QUrl url);
|
bool importSVOFromURL(const QString& urlString);
|
||||||
|
|
||||||
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
||||||
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
|
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
|
||||||
|
@ -307,6 +310,10 @@ public:
|
||||||
QString getScriptsLocation();
|
QString getScriptsLocation();
|
||||||
void setScriptsLocation(const QString& scriptsLocation);
|
void setScriptsLocation(const QString& scriptsLocation);
|
||||||
|
|
||||||
|
void initializeAcceptedFiles();
|
||||||
|
bool canAcceptURL(const QString& url);
|
||||||
|
bool acceptURL(const QString& url);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/// Fired when we're simulating; allows external parties to hook in.
|
/// Fired when we're simulating; allows external parties to hook in.
|
||||||
|
@ -341,7 +348,9 @@ public slots:
|
||||||
void loadDialog();
|
void loadDialog();
|
||||||
void loadScriptURLDialog();
|
void loadScriptURLDialog();
|
||||||
void toggleLogDialog();
|
void toggleLogDialog();
|
||||||
void askToLoadScript(const QString& scriptFilenameOrURL);
|
bool acceptSnapshot(const QString& urlString);
|
||||||
|
bool askToSetAvatarUrl(const QString& url);
|
||||||
|
bool askToLoadScript(const QString& scriptFilenameOrURL);
|
||||||
ScriptEngine* loadScript(const QString& scriptFilename = QString(), bool isUserLoaded = true,
|
ScriptEngine* loadScript(const QString& scriptFilename = QString(), bool isUserLoaded = true,
|
||||||
bool loadScriptFromEditor = false, bool activateMainWindow = false);
|
bool loadScriptFromEditor = false, bool activateMainWindow = false);
|
||||||
void scriptFinished(const QString& scriptName);
|
void scriptFinished(const QString& scriptName);
|
||||||
|
@ -595,6 +604,8 @@ private:
|
||||||
|
|
||||||
QWidget* _fullscreenMenuWidget = new QWidget();
|
QWidget* _fullscreenMenuWidget = new QWidget();
|
||||||
int _menuBarHeight;
|
int _menuBarHeight;
|
||||||
|
|
||||||
|
QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Application_h
|
#endif // hifi_Application_h
|
||||||
|
|
|
@ -117,9 +117,15 @@ void DatagramProcessor::processDatagrams() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PacketTypeDomainConnectionDenied: {
|
case PacketTypeDomainConnectionDenied: {
|
||||||
|
int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainConnectionDenied);
|
||||||
|
QDataStream packetStream(QByteArray(incomingPacket.constData() + headerSize,
|
||||||
|
incomingPacket.size() - headerSize));
|
||||||
|
QString reason;
|
||||||
|
packetStream >> reason;
|
||||||
|
|
||||||
// output to the log so the user knows they got a denied connection request
|
// output to the log so the user knows they got a denied connection request
|
||||||
// and check and signal for an access token so that we can make sure they are logged in
|
// and check and signal for an access token so that we can make sure they are logged in
|
||||||
qDebug() << "The domain-server denied a connection request.";
|
qDebug() << "The domain-server denied a connection request: " << reason;
|
||||||
qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature.";
|
qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature.";
|
||||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
AccountManager::getInstance().checkAndSignalForAccessToken();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -170,8 +170,8 @@ void GLCanvas::wheelEvent(QWheelEvent* event) {
|
||||||
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
|
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
|
||||||
const QMimeData* mimeData = event->mimeData();
|
const QMimeData* mimeData = event->mimeData();
|
||||||
foreach (QUrl url, mimeData->urls()) {
|
foreach (QUrl url, mimeData->urls()) {
|
||||||
auto lower = url.path().toLower();
|
auto urlString = url.toString();
|
||||||
if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION) || lower.endsWith(JS_EXTENSION)) {
|
if (Application::getInstance()->canAcceptURL(urlString)) {
|
||||||
event->acceptProposedAction();
|
event->acceptProposedAction();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1029,6 +1029,27 @@ void MyAvatar::renderBody(ViewFrustum* renderFrustum, RenderMode renderMode, boo
|
||||||
return; // wait until both models are loaded
|
return; // wait until both models are loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Camera *camera = Application::getInstance()->getCamera();
|
||||||
|
const glm::vec3 cameraPos = camera->getPosition();
|
||||||
|
|
||||||
|
// Set near clip distance according to skeleton model dimensions if first person and there is no separate head model.
|
||||||
|
if (shouldRenderHead(cameraPos, renderMode) || !getHead()->getFaceModel().getURL().isEmpty()) {
|
||||||
|
renderFrustum->setNearClip(DEFAULT_NEAR_CLIP);
|
||||||
|
} else {
|
||||||
|
float clipDistance = _skeletonModel.getHeadClipDistance();
|
||||||
|
if (OculusManager::isConnected()) {
|
||||||
|
// If avatar is horizontally in front of camera, increase clip distance by the amount it is in front.
|
||||||
|
glm::vec3 cameraToAvatar = _position - cameraPos;
|
||||||
|
cameraToAvatar.y = 0.0f;
|
||||||
|
glm::vec3 cameraLookAt = camera->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||||
|
float headOffset = glm::dot(cameraLookAt, cameraToAvatar);
|
||||||
|
if (headOffset > 0) {
|
||||||
|
clipDistance += headOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderFrustum->setNearClip(clipDistance);
|
||||||
|
}
|
||||||
|
|
||||||
// Render the body's voxels and head
|
// Render the body's voxels and head
|
||||||
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
||||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||||
|
@ -1040,8 +1061,6 @@ void MyAvatar::renderBody(ViewFrustum* renderFrustum, RenderMode renderMode, boo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render head so long as the camera isn't inside it
|
// Render head so long as the camera isn't inside it
|
||||||
const Camera *camera = Application::getInstance()->getCamera();
|
|
||||||
const glm::vec3 cameraPos = camera->getPosition();
|
|
||||||
if (shouldRenderHead(cameraPos, renderMode)) {
|
if (shouldRenderHead(cameraPos, renderMode)) {
|
||||||
getHead()->render(1.0f, renderFrustum, modelRenderMode, postLighting);
|
getHead()->render(1.0f, renderFrustum, modelRenderMode, postLighting);
|
||||||
}
|
}
|
||||||
|
@ -1357,6 +1376,9 @@ void MyAvatar::updatePositionWithPhysics(float deltaTime) {
|
||||||
|
|
||||||
// rotate back into world-frame
|
// rotate back into world-frame
|
||||||
_velocity = rotation * newLocalVelocity;
|
_velocity = rotation * newLocalVelocity;
|
||||||
|
|
||||||
|
_velocity += _thrust * deltaTime;
|
||||||
|
_thrust = glm::vec3(0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) {
|
void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) {
|
||||||
|
|
|
@ -37,7 +37,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
|
||||||
_defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)),
|
_defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)),
|
||||||
_standingFoot(NO_FOOT),
|
_standingFoot(NO_FOOT),
|
||||||
_standingOffset(0.0f),
|
_standingOffset(0.0f),
|
||||||
_clampedFootPosition(0.0f)
|
_clampedFootPosition(0.0f),
|
||||||
|
_headClipDistance(DEFAULT_NEAR_CLIP)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +79,10 @@ void SkeletonModel::setJointStates(QVector<JointState> states) {
|
||||||
buildShapes();
|
buildShapes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Extents meshExtents = getMeshExtents();
|
||||||
|
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
|
||||||
|
_headClipDistance = std::max(_headClipDistance, DEFAULT_NEAR_CLIP);
|
||||||
|
|
||||||
emit skeletonLoaded();
|
emit skeletonLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,8 @@ public:
|
||||||
|
|
||||||
bool hasSkeleton();
|
bool hasSkeleton();
|
||||||
|
|
||||||
|
float getHeadClipDistance() const { return _headClipDistance; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void skeletonLoaded();
|
void skeletonLoaded();
|
||||||
|
@ -160,6 +162,8 @@ private:
|
||||||
int _standingFoot;
|
int _standingFoot;
|
||||||
glm::vec3 _standingOffset;
|
glm::vec3 _standingOffset;
|
||||||
glm::vec3 _clampedFootPosition;
|
glm::vec3 _clampedFootPosition;
|
||||||
|
|
||||||
|
float _headClipDistance; // Near clip distance to use if no separate head model
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_SkeletonModel_h
|
#endif // hifi_SkeletonModel_h
|
||||||
|
|
|
@ -32,22 +32,13 @@ void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumbe
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) {
|
bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) {
|
||||||
|
QString urlString = request.url().toString();
|
||||||
if (!request.url().toString().startsWith(HIFI_URL_SCHEME)) {
|
if (Application::getInstance()->canAcceptURL(urlString)) {
|
||||||
if (request.url().path().toLower().endsWith(SVO_EXTENSION)) {
|
if (Application::getInstance()->acceptURL(urlString)) {
|
||||||
Application::getInstance()->importSVOFromURL(request.url());
|
return false; // we handled it, so QWebPage doesn't need to handle it
|
||||||
return false;
|
}
|
||||||
} else if (request.url().path().toLower().endsWith(JS_EXTENSION)) {
|
|
||||||
Application::getInstance()->askToLoadScript(request.url().toString());
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
// this is a hifi URL - have the AddressManager handle it
|
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
|
|
||||||
Qt::AutoConnection, Q_ARG(const QString&, request.url().toString()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DataWebPage::userAgentForUrl(const QUrl& url) const {
|
QString DataWebPage::userAgentForUrl(const QUrl& url) const {
|
||||||
|
|
|
@ -410,13 +410,30 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI
|
||||||
if (entityItem->getType() == EntityTypes::Model) {
|
if (entityItem->getType() == EntityTypes::Model) {
|
||||||
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
|
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
|
||||||
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
|
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
|
||||||
assert(modelEntityItem); // we need this!!!
|
|
||||||
|
|
||||||
result = modelEntityItem->getModel(this);
|
result = modelEntityItem->getModel(this);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FBXGeometry* EntityTreeRenderer::getCollisionGeometryForEntity(const EntityItem* entityItem) {
|
||||||
|
const FBXGeometry* result = NULL;
|
||||||
|
|
||||||
|
if (entityItem->getType() == EntityTypes::Model) {
|
||||||
|
const RenderableModelEntityItem* constModelEntityItem = dynamic_cast<const RenderableModelEntityItem*>(entityItem);
|
||||||
|
if (constModelEntityItem->hasCollisionModel()) {
|
||||||
|
RenderableModelEntityItem* modelEntityItem = const_cast<RenderableModelEntityItem*>(constModelEntityItem);
|
||||||
|
Model* model = modelEntityItem->getModel(this);
|
||||||
|
if (model) {
|
||||||
|
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = model->getCollisionGeometry();
|
||||||
|
if (!collisionNetworkGeometry.isNull()) {
|
||||||
|
result = &collisionNetworkGeometry->getFBXGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::renderElementProxy(EntityTreeElement* entityTreeElement) {
|
void EntityTreeRenderer::renderElementProxy(EntityTreeElement* entityTreeElement) {
|
||||||
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter();
|
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter();
|
||||||
float elementSize = entityTreeElement->getScale();
|
float elementSize = entityTreeElement->getScale();
|
||||||
|
@ -591,7 +608,7 @@ void EntityTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, co
|
||||||
static_cast<EntityTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
|
static_cast<EntityTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
Model* EntityTreeRenderer::allocateModel(const QString& url) {
|
Model* EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) {
|
||||||
Model* model = NULL;
|
Model* model = NULL;
|
||||||
// Make sure we only create and delete models on the thread that owns the EntityTreeRenderer
|
// Make sure we only create and delete models on the thread that owns the EntityTreeRenderer
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
|
@ -604,10 +621,11 @@ Model* EntityTreeRenderer::allocateModel(const QString& url) {
|
||||||
model = new Model();
|
model = new Model();
|
||||||
model->init();
|
model->init();
|
||||||
model->setURL(QUrl(url));
|
model->setURL(QUrl(url));
|
||||||
|
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
Model* EntityTreeRenderer::updateModel(Model* original, const QString& newUrl) {
|
Model* EntityTreeRenderer::updateModel(Model* original, const QString& newUrl, const QString& collisionUrl) {
|
||||||
Model* model = NULL;
|
Model* model = NULL;
|
||||||
|
|
||||||
// The caller shouldn't call us if the URL doesn't need to change. But if they
|
// The caller shouldn't call us if the URL doesn't need to change. But if they
|
||||||
|
@ -636,6 +654,7 @@ Model* EntityTreeRenderer::updateModel(Model* original, const QString& newUrl) {
|
||||||
model = new Model();
|
model = new Model();
|
||||||
model->init();
|
model->init();
|
||||||
model->setURL(QUrl(newUrl));
|
model->setURL(QUrl(newUrl));
|
||||||
|
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,15 +59,16 @@ public:
|
||||||
|
|
||||||
virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem);
|
virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem);
|
||||||
virtual const Model* getModelForEntityItem(const EntityItem* entityItem);
|
virtual const Model* getModelForEntityItem(const EntityItem* entityItem);
|
||||||
|
virtual const FBXGeometry* getCollisionGeometryForEntity(const EntityItem* entityItem);
|
||||||
|
|
||||||
/// clears the tree
|
/// clears the tree
|
||||||
virtual void clear();
|
virtual void clear();
|
||||||
|
|
||||||
/// if a renderable entity item needs a model, we will allocate it for them
|
/// if a renderable entity item needs a model, we will allocate it for them
|
||||||
Q_INVOKABLE Model* allocateModel(const QString& url);
|
Q_INVOKABLE Model* allocateModel(const QString& url, const QString& collisionUrl);
|
||||||
|
|
||||||
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
|
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
|
||||||
Q_INVOKABLE Model* updateModel(Model* original, const QString& newUrl);
|
Q_INVOKABLE Model* updateModel(Model* original, const QString& newUrl, const QString& collisionUrl);
|
||||||
|
|
||||||
/// if a renderable entity item is done with a model, it should return it to us
|
/// if a renderable entity item is done with a model, it should return it to us
|
||||||
void releaseModel(Model* model);
|
void releaseModel(Model* model);
|
||||||
|
|
|
@ -223,10 +223,10 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
||||||
// if we have a previously allocated model, but it's URL doesn't match
|
// if we have a previously allocated model, but it's URL doesn't match
|
||||||
// then we need to let our renderer update our model for us.
|
// then we need to let our renderer update our model for us.
|
||||||
if (_model && QUrl(getModelURL()) != _model->getURL()) {
|
if (_model && QUrl(getModelURL()) != _model->getURL()) {
|
||||||
result = _model = _myRenderer->updateModel(_model, getModelURL());
|
result = _model = _myRenderer->updateModel(_model, getModelURL(), getCollisionModelURL());
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
} else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one
|
} else if (!_model) { // if we don't yet have a model, then we want our renderer to allocate one
|
||||||
result = _model = _myRenderer->allocateModel(getModelURL());
|
result = _model = _myRenderer->allocateModel(getModelURL(), getCollisionModelURL());
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
} else { // we already have the model we want...
|
} else { // we already have the model we want...
|
||||||
result = _model;
|
result = _model;
|
||||||
|
@ -267,36 +267,32 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderableModelEntityItem::isReadyToComputeShape() {
|
bool RenderableModelEntityItem::isReadyToComputeShape() {
|
||||||
if (_collisionModelURL == "") {
|
|
||||||
|
if (!_model) {
|
||||||
|
return false; // hmm...
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_model->getCollisionURL().isEmpty()) {
|
||||||
// no model url, so we're ready to compute a shape.
|
// no model url, so we're ready to compute a shape.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! _collisionNetworkGeometry.isNull() && _collisionNetworkGeometry->isLoadedWithTextures()) {
|
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
|
||||||
// we have a _collisionModelURL AND a _collisionNetworkGeometry AND it's fully loaded.
|
if (! collisionNetworkGeometry.isNull() && collisionNetworkGeometry->isLoadedWithTextures()) {
|
||||||
|
// we have a _collisionModelURL AND a collisionNetworkGeometry AND it's fully loaded.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_collisionNetworkGeometry.isNull()) {
|
|
||||||
// we have a _collisionModelURL but we don't yet have a _collisionNetworkGeometry.
|
|
||||||
_collisionNetworkGeometry =
|
|
||||||
DependencyManager::get<GeometryCache>()->getGeometry(_collisionModelURL, QUrl(), false, false);
|
|
||||||
|
|
||||||
if (! _collisionNetworkGeometry.isNull() && _collisionNetworkGeometry->isLoadedWithTextures()) {
|
|
||||||
// shortcut in case it's already loaded.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the model is still being downloaded.
|
// the model is still being downloaded.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
if (_collisionModelURL == "") {
|
if (_model->getCollisionURL().isEmpty()) {
|
||||||
info.setParams(getShapeType(), 0.5f * getDimensions());
|
info.setParams(getShapeType(), 0.5f * getDimensions());
|
||||||
} else {
|
} else {
|
||||||
const FBXGeometry& fbxGeometry = _collisionNetworkGeometry->getFBXGeometry();
|
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
|
||||||
|
const FBXGeometry& fbxGeometry = collisionNetworkGeometry->getFBXGeometry();
|
||||||
|
|
||||||
_points.clear();
|
_points.clear();
|
||||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||||
|
@ -310,9 +306,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
|
|
||||||
ShapeType RenderableModelEntityItem::getShapeType() const {
|
ShapeType RenderableModelEntityItem::getShapeType() const {
|
||||||
// XXX make hull an option in edit.js ?
|
// XXX make hull an option in edit.js ?
|
||||||
if (_collisionModelURL != "") {
|
if (!_model || _model->getCollisionURL().isEmpty()) {
|
||||||
return SHAPE_TYPE_CONVEX_HULL;
|
|
||||||
} else {
|
|
||||||
return _shapeType;
|
return _shapeType;
|
||||||
|
} else {
|
||||||
|
return SHAPE_TYPE_CONVEX_HULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,7 @@ public:
|
||||||
_needsInitialSimulation(true),
|
_needsInitialSimulation(true),
|
||||||
_needsModelReload(true),
|
_needsModelReload(true),
|
||||||
_myRenderer(NULL),
|
_myRenderer(NULL),
|
||||||
_originalTexturesRead(false),
|
_originalTexturesRead(false) { }
|
||||||
_collisionNetworkGeometry(QSharedPointer<NetworkGeometry>()) { }
|
|
||||||
|
|
||||||
virtual ~RenderableModelEntityItem();
|
virtual ~RenderableModelEntityItem();
|
||||||
|
|
||||||
|
@ -67,8 +66,6 @@ private:
|
||||||
QString _currentTextures;
|
QString _currentTextures;
|
||||||
QStringList _originalTextures;
|
QStringList _originalTextures;
|
||||||
bool _originalTexturesRead;
|
bool _originalTexturesRead;
|
||||||
|
|
||||||
QSharedPointer<NetworkGeometry> _collisionNetworkGeometry;
|
|
||||||
QVector<glm::vec3> _points;
|
QVector<glm::vec3> _points;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ class EntityItemFBXService {
|
||||||
public:
|
public:
|
||||||
virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem) = 0;
|
virtual const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem) = 0;
|
||||||
virtual const Model* getModelForEntityItem(const EntityItem* entityItem) = 0;
|
virtual const Model* getModelForEntityItem(const EntityItem* entityItem) = 0;
|
||||||
|
virtual const FBXGeometry* getCollisionGeometryForEntity(const EntityItem* entityItem) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include "DependencyManager.h"
|
#include "DependencyManager.h"
|
||||||
#include "Node.h"
|
|
||||||
#include "SimpleMovingAverage.h"
|
#include "SimpleMovingAverage.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -263,8 +263,10 @@ qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram,
|
||||||
}
|
}
|
||||||
|
|
||||||
emit dataSent(destinationNode->getType(), datagram.size());
|
emit dataSent(destinationNode->getType(), datagram.size());
|
||||||
|
auto bytesWritten = writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret());
|
||||||
return writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret());
|
// Keep track of per-destination-node bandwidth
|
||||||
|
destinationNode->recordBytesSent(bytesWritten);
|
||||||
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
// didn't have a destinationNode to send to, return 0
|
// didn't have a destinationNode to send to, return 0
|
||||||
|
|
|
@ -152,6 +152,17 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename PredLambda, typename NodeLambda>
|
||||||
|
void eachMatchingNode(PredLambda predicate, NodeLambda functor) {
|
||||||
|
QReadLocker readLock(&_nodeMutex);
|
||||||
|
|
||||||
|
for (NodeHash::const_iterator it = _nodeHash.cbegin(); it != _nodeHash.cend(); ++it) {
|
||||||
|
if (predicate(it->second)) {
|
||||||
|
functor(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BreakableNodeLambda>
|
template<typename BreakableNodeLambda>
|
||||||
void eachNodeBreakable(BreakableNodeLambda functor) {
|
void eachNodeBreakable(BreakableNodeLambda functor) {
|
||||||
QReadLocker readLock(&_nodeMutex);
|
QReadLocker readLock(&_nodeMutex);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
|
|
||||||
#include "NetworkPeer.h"
|
#include "NetworkPeer.h"
|
||||||
|
#include "BandwidthRecorder.h"
|
||||||
|
|
||||||
NetworkPeer::NetworkPeer() :
|
NetworkPeer::NetworkPeer() :
|
||||||
_uuid(),
|
_uuid(),
|
||||||
|
@ -97,3 +98,36 @@ QDebug operator<<(QDebug debug, const NetworkPeer &peer) {
|
||||||
<< "- local:" << peer.getLocalSocket();
|
<< "- local:" << peer.getLocalSocket();
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME this is a temporary implementation to determine if this is the right approach.
|
||||||
|
// If so, migrate the BandwidthRecorder into the NetworkPeer class
|
||||||
|
using BandwidthRecorderPtr = QSharedPointer<BandwidthRecorder>;
|
||||||
|
static QHash<QUuid, BandwidthRecorderPtr> PEER_BANDWIDTH;
|
||||||
|
|
||||||
|
BandwidthRecorder& getBandwidthRecorder(const QUuid & uuid) {
|
||||||
|
if (!PEER_BANDWIDTH.count(uuid)) {
|
||||||
|
PEER_BANDWIDTH.insert(uuid, BandwidthRecorderPtr(new BandwidthRecorder()));
|
||||||
|
}
|
||||||
|
return *PEER_BANDWIDTH[uuid].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkPeer::recordBytesSent(int count) {
|
||||||
|
auto& bw = getBandwidthRecorder(_uuid);
|
||||||
|
bw.updateOutboundData(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkPeer::recordBytesReceived(int count) {
|
||||||
|
auto& bw = getBandwidthRecorder(_uuid);
|
||||||
|
bw.updateInboundData(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
float NetworkPeer::getOutboundBandwidth() {
|
||||||
|
auto& bw = getBandwidthRecorder(_uuid);
|
||||||
|
return bw.getAverageOutputKilobitsPerSecond(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float NetworkPeer::getInboundBandwidth() {
|
||||||
|
auto& bw = getBandwidthRecorder(_uuid);
|
||||||
|
return bw.getAverageInputKilobitsPerSecond(0);
|
||||||
|
}
|
||||||
|
|
|
@ -55,6 +55,12 @@ public:
|
||||||
void incrementConnectionAttempts() { ++_connectionAttempts; }
|
void incrementConnectionAttempts() { ++_connectionAttempts; }
|
||||||
void resetConnectionAttemps() { _connectionAttempts = 0; }
|
void resetConnectionAttemps() { _connectionAttempts = 0; }
|
||||||
|
|
||||||
|
void recordBytesSent(int count);
|
||||||
|
void recordBytesReceived(int count);
|
||||||
|
|
||||||
|
float getOutboundBandwidth();
|
||||||
|
float getInboundBandwidth();
|
||||||
|
|
||||||
friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer);
|
friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer);
|
||||||
friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer);
|
friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer);
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -298,6 +298,7 @@ void PhysicsEngine::stepSimulation() {
|
||||||
_clock.reset();
|
_clock.reset();
|
||||||
float timeStep = btMin(dt, MAX_TIMESTEP);
|
float timeStep = btMin(dt, MAX_TIMESTEP);
|
||||||
|
|
||||||
|
_avatarData->lockForRead();
|
||||||
if (_avatarData->isPhysicsEnabled()) {
|
if (_avatarData->isPhysicsEnabled()) {
|
||||||
// update character controller
|
// update character controller
|
||||||
glm::quat rotation = _avatarData->getOrientation();
|
glm::quat rotation = _avatarData->getOrientation();
|
||||||
|
@ -307,6 +308,7 @@ void PhysicsEngine::stepSimulation() {
|
||||||
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
|
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
|
||||||
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
|
_characterController->setVelocityForTimeInterval(walkVelocity, timeStep);
|
||||||
}
|
}
|
||||||
|
_avatarData->unlock();
|
||||||
|
|
||||||
// This is step (2).
|
// This is step (2).
|
||||||
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||||
|
|
|
@ -106,11 +106,11 @@ public:
|
||||||
/// \param delayLoad if true, don't load the model immediately; wait until actually requested
|
/// \param delayLoad if true, don't load the model immediately; wait until actually requested
|
||||||
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(),
|
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(),
|
||||||
bool retainCurrent = false, bool delayLoad = false);
|
bool retainCurrent = false, bool delayLoad = false);
|
||||||
|
const QUrl& getURL() const { return _url; }
|
||||||
|
|
||||||
// Set the model to use for collisions
|
// Set the model to use for collisions
|
||||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||||
|
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||||
const QUrl& getURL() const { return _url; }
|
|
||||||
|
|
||||||
/// Sets the distance parameter used for LOD computations.
|
/// Sets the distance parameter used for LOD computations.
|
||||||
void setLODDistance(float distance) { _lodDistance = distance; }
|
void setLODDistance(float distance) { _lodDistance = distance; }
|
||||||
|
@ -133,6 +133,9 @@ public:
|
||||||
/// Returns a reference to the shared geometry.
|
/// Returns a reference to the shared geometry.
|
||||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||||
|
|
||||||
|
/// Returns a reference to the shared collision geometry.
|
||||||
|
const QSharedPointer<NetworkGeometry> getCollisionGeometry() {return _collisionGeometry; }
|
||||||
|
|
||||||
/// Returns the number of joint states in the model.
|
/// Returns the number of joint states in the model.
|
||||||
int getJointStateCount() const { return _jointStates.size(); }
|
int getJointStateCount() const { return _jointStates.size(); }
|
||||||
|
|
||||||
|
|
28
libraries/shared/src/TryLocker.h
Normal file
28
libraries/shared/src/TryLocker.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// TryLocker.h
|
||||||
|
// libraries/shared/src
|
||||||
|
//
|
||||||
|
// Created by Brad Davis on 2015/03/16.
|
||||||
|
// Copyright 2015 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_TryLocker_h
|
||||||
|
#define hifi_TryLocker_h
|
||||||
|
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
class MutexTryLocker {
|
||||||
|
QMutex& _mutex;
|
||||||
|
bool _locked{ false };
|
||||||
|
public:
|
||||||
|
MutexTryLocker(QMutex &m) : _mutex(m), _locked(m.tryLock()) {}
|
||||||
|
~MutexTryLocker() { if (_locked) _mutex.unlock(); }
|
||||||
|
bool isLocked() {
|
||||||
|
return _locked;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_TryLocker_h
|
|
@ -4,5 +4,6 @@ list(REMOVE_ITEM TEST_SUBDIRS "CMakeFiles")
|
||||||
foreach(DIR ${TEST_SUBDIRS})
|
foreach(DIR ${TEST_SUBDIRS})
|
||||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}")
|
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}")
|
||||||
add_subdirectory(${DIR})
|
add_subdirectory(${DIR})
|
||||||
|
set_target_properties("${DIR}-tests" PROPERTIES FOLDER "Tests")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
|
@ -1,8 +1,12 @@
|
||||||
# add the tool directories
|
# add the tool directories
|
||||||
add_subdirectory(mtc)
|
add_subdirectory(mtc)
|
||||||
|
set_target_properties(mtc PROPERTIES FOLDER "Tools")
|
||||||
add_subdirectory(scribe)
|
add_subdirectory(scribe)
|
||||||
|
set_target_properties(scribe PROPERTIES FOLDER "Tools")
|
||||||
|
|
||||||
|
|
||||||
find_package(VHACD)
|
find_package(VHACD)
|
||||||
if(VHACD_FOUND)
|
if(VHACD_FOUND)
|
||||||
add_subdirectory(vhacd)
|
add_subdirectory(vhacd)
|
||||||
|
set_target_properties(vhacd PROPERTIES FOLDER "Tools")
|
||||||
endif()
|
endif()
|
||||||
|
|
Loading…
Reference in a new issue