mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 14:47:19 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into overlay-children
This commit is contained in:
commit
607c168493
35 changed files with 565 additions and 158 deletions
|
@ -100,6 +100,63 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
|
||||
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
||||
|
||||
const int IEEE754_MANT_BITS = 23;
|
||||
const int IEEE754_EXPN_BIAS = 127;
|
||||
|
||||
//
|
||||
// for x > 0.0f, returns log2(x)
|
||||
// for x <= 0.0f, returns large negative value
|
||||
//
|
||||
// abs |error| < 8e-3, smooth (exact for x=2^N) for NPOLY=3
|
||||
// abs |error| < 2e-4, smooth (exact for x=2^N) for NPOLY=5
|
||||
// rel |error| < 0.4 from precision loss very close to 1.0f
|
||||
//
|
||||
static inline float fastlog2(float x) {
|
||||
|
||||
union { float f; int32_t i; } mant, bits = { x };
|
||||
|
||||
// split into mantissa and exponent
|
||||
mant.i = (bits.i & ((1 << IEEE754_MANT_BITS) - 1)) | (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS);
|
||||
int32_t expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS;
|
||||
|
||||
mant.f -= 1.0f;
|
||||
|
||||
// polynomial for log2(1+x) over x=[0,1]
|
||||
//x = (-0.346555386f * mant.f + 1.346555386f) * mant.f;
|
||||
x = (((-0.0821307180f * mant.f + 0.321188984f) * mant.f - 0.677784014f) * mant.f + 1.43872575f) * mant.f;
|
||||
|
||||
return x + expn;
|
||||
}
|
||||
|
||||
//
|
||||
// for -126 <= x < 128, returns exp2(x)
|
||||
//
|
||||
// rel |error| < 3e-3, smooth (exact for x=N) for NPOLY=3
|
||||
// rel |error| < 9e-6, smooth (exact for x=N) for NPOLY=5
|
||||
//
|
||||
static inline float fastexp2(float x) {
|
||||
|
||||
union { float f; int32_t i; } xi;
|
||||
|
||||
// bias such that x > 0
|
||||
x += IEEE754_EXPN_BIAS;
|
||||
//x = MAX(x, 1.0f);
|
||||
//x = MIN(x, 254.9999f);
|
||||
|
||||
// split into integer and fraction
|
||||
xi.i = (int32_t)x;
|
||||
x -= xi.i;
|
||||
|
||||
// construct exp2(xi) as a float
|
||||
xi.i <<= IEEE754_MANT_BITS;
|
||||
|
||||
// polynomial for exp2(x) over x=[0,1]
|
||||
//x = (0.339766028f * x + 0.660233972f) * x + 1.0f;
|
||||
x = (((0.0135557472f * x + 0.0520323690f) * x + 0.241379763f) * x + 0.693032121f) * x + 1.0f;
|
||||
|
||||
return x * xi.f;
|
||||
}
|
||||
|
||||
float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
||||
const AvatarAudioStream& listeningNodeStream, const glm::vec3& relativePosition, bool isEcho) {
|
||||
float gain = 1.0f;
|
||||
|
@ -148,7 +205,7 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
|||
g = (g > 1.0f) ? 1.0f : g;
|
||||
|
||||
// calculate the distance coefficient using the distance to this node
|
||||
float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
||||
float distanceCoefficient = fastexp2(fastlog2(g) * fastlog2(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
||||
|
||||
// multiply the current attenuation coefficient by the distance coefficient
|
||||
gain *= distanceCoefficient;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -182,7 +182,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
|
|||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is in group:" << groupID << " rank:"
|
||||
qDebug() << "| user-permissions: user " << verifiedUsername << "is in group:" << groupID << " rank:"
|
||||
<< rank.name << "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -117,9 +117,18 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
if (_type == MetaverseDomain) {
|
||||
// if we have a metaverse domain, we'll need an access token to heartbeat handle auto-networking
|
||||
resetAccountManagerAccessToken();
|
||||
}
|
||||
|
||||
setupAutomaticNetworking();
|
||||
if (!getID().isNull()) {
|
||||
|
||||
if (!getID().isNull() && _type != NonMetaverse) {
|
||||
// setup periodic heartbeats to metaverse API
|
||||
setupHeartbeatToMetaverse();
|
||||
|
||||
// send the first heartbeat immediately
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
|
@ -301,16 +310,22 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
// store the new ID and auto networking setting on disk
|
||||
_settingsManager.persistToFile();
|
||||
|
||||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// store the new token to the account info
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->setTemporaryDomain(id, key);
|
||||
|
||||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// change our type to reflect that we are a temporary domain now
|
||||
_type = MetaverseTemporaryDomain;
|
||||
|
||||
// update our heartbeats to use the correct id
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
setupHeartbeatToMetaverse();
|
||||
|
||||
// if we have a current ICE server address, update it in the API for the new temporary domain
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
} else {
|
||||
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
||||
<< "via domain-server relaunch or from the domain-server settings.";
|
||||
|
@ -394,6 +409,16 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
nodeList->setSessionUUID(idValueVariant->toString());
|
||||
|
||||
// if we have an ID, we'll assume we're a metaverse domain
|
||||
// now see if we think we're a temp domain (we have an API key) or a full domain
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (temporaryDomainKey.isEmpty()) {
|
||||
_type = MetaverseDomain;
|
||||
} else {
|
||||
_type = MetaverseTemporaryDomain;
|
||||
}
|
||||
|
||||
} else {
|
||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||
}
|
||||
|
@ -477,42 +502,46 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||
|
||||
resetAccountManagerAccessToken();
|
||||
|
||||
_automaticNetworkingSetting =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& domainID = getID();
|
||||
qDebug() << "Configuring automatic networking in domain-server as" << _automaticNetworkingSetting;
|
||||
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
if (_automaticNetworkingSetting != DISABLED_AUTOMATIC_NETWORKING_VALUE) {
|
||||
const QUuid& domainID = getID();
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||
this, &DomainServer::performIPAddressUpdate);
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||
this, &DomainServer::performIPAddressUpdate);
|
||||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DomainServer::setupHeartbeatToMetaverse() {
|
||||
|
@ -1139,42 +1168,45 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
|||
return;
|
||||
}
|
||||
|
||||
// check if we need to force a new temporary domain name
|
||||
switch (requestReply.error()) {
|
||||
// if we have a temporary domain with a bad token, we get a 401
|
||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString TOKEN_KEY = "api_key";
|
||||
// only attempt to grab a new temporary name if we're already a temporary domain server
|
||||
if (_type == MetaverseTemporaryDomain) {
|
||||
// check if we need to force a new temporary domain name
|
||||
switch (requestReply.error()) {
|
||||
// if we have a temporary domain with a bad token, we get a 401
|
||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString TOKEN_KEY = "api_key";
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||
|
||||
if (!tokenFailure.isNull()) {
|
||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||
if (!tokenFailure.isNull()) {
|
||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// if the domain does not (or no longer) exists, we get a 404
|
||||
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||
break;
|
||||
// otherwise, we erred on something else, and should not force a temporary domain
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// if the domain does not (or no longer) exists, we get a 404
|
||||
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||
break;
|
||||
// otherwise, we erred on something else, and should not force a temporary domain
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// halt heartbeats until we have a token
|
||||
_metaverseHeartbeatTimer->deleteLater();
|
||||
_metaverseHeartbeatTimer = nullptr;
|
||||
// halt heartbeats until we have a token
|
||||
_metaverseHeartbeatTimer->deleteLater();
|
||||
_metaverseHeartbeatTimer = nullptr;
|
||||
|
||||
// give up eventually to avoid flooding traffic
|
||||
static const int MAX_ATTEMPTS = 5;
|
||||
static int attempt = 0;
|
||||
if (++attempt < MAX_ATTEMPTS) {
|
||||
// get a new temporary name and token
|
||||
getTemporaryName(true);
|
||||
} else {
|
||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||
// give up eventually to avoid flooding traffic
|
||||
static const int MAX_ATTEMPTS = 5;
|
||||
static int attempt = 0;
|
||||
if (++attempt < MAX_ATTEMPTS) {
|
||||
// get a new temporary name and token
|
||||
getTemporaryName(true);
|
||||
} else {
|
||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,12 @@ public:
|
|||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
enum DomainType {
|
||||
NonMetaverse,
|
||||
MetaverseDomain,
|
||||
MetaverseTemporaryDomain
|
||||
};
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
@ -195,6 +201,8 @@ private:
|
|||
int _numHeartbeatDenials { 0 };
|
||||
bool _connectedToICEServer { false };
|
||||
|
||||
DomainType _type { DomainType::NonMetaverse };
|
||||
|
||||
friend class DomainGatekeeper;
|
||||
friend class DomainMetadata;
|
||||
};
|
||||
|
|
|
@ -356,7 +356,7 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap&
|
|||
if (nameKey.first.toLower() != groupNameLower) {
|
||||
continue;
|
||||
}
|
||||
QUuid groupID = _groupIDs[groupNameLower];
|
||||
QUuid groupID = _groupIDs[groupNameLower.toLower()];
|
||||
QUuid rankID = nameKey.second;
|
||||
GroupRank rank = _groupRanks[groupID][rankID];
|
||||
if (rank.order == 0) {
|
||||
|
@ -1477,14 +1477,14 @@ void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& r
|
|||
|
||||
void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) {
|
||||
if (rankID != QUuid()) {
|
||||
_groupMembership[name][groupID] = rankID;
|
||||
_groupMembership[name.toLower()][groupID] = rankID;
|
||||
} else {
|
||||
_groupMembership[name].remove(groupID);
|
||||
_groupMembership[name.toLower()].remove(groupID);
|
||||
}
|
||||
}
|
||||
|
||||
QUuid DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) {
|
||||
const QHash<QUuid, QUuid>& groupsForName = _groupMembership[name];
|
||||
const QHash<QUuid, QUuid>& groupsForName = _groupMembership[name.toLower()];
|
||||
if (groupsForName.contains(groupID)) {
|
||||
return groupsForName[groupID];
|
||||
}
|
||||
|
@ -1528,7 +1528,7 @@ void DomainServerSettingsManager::debugDumpGroupsState() {
|
|||
|
||||
qDebug() << "_groupIDs:";
|
||||
foreach (QString groupName, _groupIDs.keys()) {
|
||||
qDebug() << "| " << groupName << "==>" << _groupIDs[groupName];
|
||||
qDebug() << "| " << groupName << "==>" << _groupIDs[groupName.toLower()];
|
||||
}
|
||||
|
||||
qDebug() << "_groupNames:";
|
||||
|
@ -1548,7 +1548,7 @@ void DomainServerSettingsManager::debugDumpGroupsState() {
|
|||
|
||||
qDebug() << "_groupMembership";
|
||||
foreach (QString userName, _groupMembership.keys()) {
|
||||
QHash<QUuid, QUuid>& groupsForUser = _groupMembership[userName];
|
||||
QHash<QUuid, QUuid>& groupsForUser = _groupMembership[userName.toLower()];
|
||||
QString line = "";
|
||||
foreach (QUuid groupID, groupsForUser.keys()) {
|
||||
line += " g=" + groupID.toString() + ",r=" + groupsForUser[groupID].toString();
|
||||
|
|
|
@ -84,7 +84,7 @@ public:
|
|||
QList<QUuid> getBlacklistGroupIDs();
|
||||
|
||||
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
|
||||
void clearGroupMemberships(const QString& name) { _groupMembership[name].clear(); }
|
||||
void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); }
|
||||
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
|
||||
QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member
|
||||
|
||||
|
|
|
@ -314,6 +314,14 @@ ScrollingWindow {
|
|||
});
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: doUploadTimer
|
||||
property var url
|
||||
property bool isConnected: false
|
||||
interval: 5
|
||||
repeat: false
|
||||
running: false
|
||||
}
|
||||
|
||||
property var uploadOpen: false;
|
||||
Timer {
|
||||
|
@ -366,6 +374,10 @@ ScrollingWindow {
|
|||
}, dropping);
|
||||
}
|
||||
|
||||
function initiateUpload(url) {
|
||||
doUpload(doUploadTimer.url, false);
|
||||
}
|
||||
|
||||
if (fileUrl) {
|
||||
doUpload(fileUrl, true);
|
||||
} else {
|
||||
|
@ -373,12 +385,21 @@ ScrollingWindow {
|
|||
selectDirectory: false,
|
||||
dir: currentDirectory
|
||||
});
|
||||
|
||||
browser.canceled.connect(function() {
|
||||
uploadOpen = false;
|
||||
});
|
||||
|
||||
browser.selectedFile.connect(function(url) {
|
||||
currentDirectory = browser.dir;
|
||||
doUpload(url, false);
|
||||
|
||||
// Initiate upload from a timer so that file browser dialog can close beforehand.
|
||||
doUploadTimer.url = url;
|
||||
if (!doUploadTimer.isConnected) {
|
||||
doUploadTimer.triggered.connect(function() { initiateUpload(); });
|
||||
doUploadTimer.isConnected = true;
|
||||
}
|
||||
doUploadTimer.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -739,7 +739,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket);
|
||||
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
ResourceCache::setRequestLimit(MAX_CONCURRENT_RESOURCE_DOWNLOADS);
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
|
||||
bool success;
|
||||
int concurrentDownloads = concurrentDownloadsStr.toInt(&success);
|
||||
if (!success) {
|
||||
concurrentDownloads = MAX_CONCURRENT_RESOURCE_DOWNLOADS;
|
||||
}
|
||||
ResourceCache::setRequestLimit(concurrentDownloads);
|
||||
|
||||
_glWidget = new GLCanvas();
|
||||
getApplicationCompositor().setRenderingWidget(_glWidget);
|
||||
|
@ -811,6 +818,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
}
|
||||
UserActivityLogger::getInstance().logAction("launch", properties);
|
||||
|
||||
_connectionMonitor.init();
|
||||
|
||||
// Tell our entity edit sender about our known jurisdictions
|
||||
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
|
||||
|
@ -3240,6 +3248,18 @@ void Application::init() {
|
|||
getEntities()->setViewFrustum(_viewFrustum);
|
||||
}
|
||||
|
||||
getEntities()->setEntityLoadingPriorityFunction([this](const EntityItem& item) {
|
||||
auto dims = item.getDimensions();
|
||||
auto maxSize = glm::max(dims.x, dims.y, dims.z);
|
||||
|
||||
if (maxSize <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
auto distance = glm::distance(getMyAvatar()->getPosition(), item.getPosition());
|
||||
return atan2(maxSize, distance);
|
||||
});
|
||||
|
||||
ObjectMotionState::setShapeManager(&_shapeManager);
|
||||
_physicsEngine->init();
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "avatar/MyAvatar.h"
|
||||
#include "Bookmarks.h"
|
||||
#include "Camera.h"
|
||||
#include "ConnectionMonitor.h"
|
||||
#include "FileLogger.h"
|
||||
#include "gpu/Context.h"
|
||||
#include "Menu.h"
|
||||
|
@ -562,6 +563,9 @@ private:
|
|||
bool _recentlyClearedDomain { false };
|
||||
|
||||
QString _returnFromFullScreenMirrorTo;
|
||||
|
||||
ConnectionMonitor _connectionMonitor;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
52
interface/src/ConnectionMonitor.cpp
Normal file
52
interface/src/ConnectionMonitor.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// ConnectionMonitor.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 8/4/15.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "ConnectionMonitor.h"
|
||||
|
||||
#include "ui/DialogsManager.h"
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <DomainHandler.h>
|
||||
#include <AddressManager.h>
|
||||
|
||||
static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000;
|
||||
|
||||
void ConnectionMonitor::init() {
|
||||
// Connect to domain disconnected message
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &ConnectionMonitor::disconnectedFromDomain);
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::connectedToDomain);
|
||||
|
||||
// Connect to AddressManager::hostChanged
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &ConnectionMonitor::hostChanged);
|
||||
|
||||
_timer.setSingleShot(true);
|
||||
_timer.setInterval(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
|
||||
_timer.start();
|
||||
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
connect(&_timer, &QTimer::timeout, dialogsManager.data(), &DialogsManager::showAddressBar);
|
||||
}
|
||||
|
||||
void ConnectionMonitor::disconnectedFromDomain() {
|
||||
_timer.start();
|
||||
}
|
||||
|
||||
void ConnectionMonitor::connectedToDomain(const QString& name) {
|
||||
_timer.stop();
|
||||
}
|
||||
|
||||
void ConnectionMonitor::hostChanged(const QString& name) {
|
||||
_timer.start();
|
||||
}
|
34
interface/src/ConnectionMonitor.h
Normal file
34
interface/src/ConnectionMonitor.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// ConnectionMonitor.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 8/4/15.
|
||||
// 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_ConnectionMonitor_h
|
||||
#define hifi_ConnectionMonitor_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class QString;
|
||||
|
||||
class ConnectionMonitor : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
void init();
|
||||
|
||||
private slots:
|
||||
void disconnectedFromDomain();
|
||||
void connectedToDomain(const QString& name);
|
||||
void hostChanged(const QString& name);
|
||||
|
||||
private:
|
||||
QTimer _timer;
|
||||
};
|
||||
|
||||
#endif // hifi_ConnectionMonitor_h
|
|
@ -98,6 +98,7 @@ Avatar::Avatar(RigPointer rig) :
|
|||
_headData = static_cast<HeadData*>(new Head(this));
|
||||
|
||||
_skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);
|
||||
connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
|
||||
}
|
||||
|
||||
Avatar::~Avatar() {
|
||||
|
@ -298,7 +299,9 @@ void Avatar::simulate(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("head");
|
||||
glm::vec3 headPosition = getPosition();
|
||||
_skeletonModel->getHeadPosition(headPosition);
|
||||
if (!_skeletonModel->getHeadPosition(headPosition)) {
|
||||
headPosition = getPosition();
|
||||
}
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(getUniformScale());
|
||||
|
@ -306,6 +309,7 @@ void Avatar::simulate(float deltaTime) {
|
|||
}
|
||||
} else {
|
||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
||||
getHead()->setPosition(getPosition());
|
||||
_skeletonModel->simulate(deltaTime, false);
|
||||
}
|
||||
|
||||
|
@ -916,6 +920,17 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
}
|
||||
}
|
||||
|
||||
void Avatar::setModelURLFinished(bool success) {
|
||||
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
|
||||
qDebug() << "Using default after failing to load Avatar model: " << _skeletonModelURL;
|
||||
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
|
||||
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
|
||||
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
|
||||
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create new model, can return an instance of a SoftAttachmentModel rather then Model
|
||||
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
|
||||
if (isSoft) {
|
||||
|
|
|
@ -184,6 +184,8 @@ public slots:
|
|||
glm::vec3 getRightPalmPosition() const;
|
||||
glm::quat getRightPalmRotation() const;
|
||||
|
||||
void setModelURLFinished(bool success);
|
||||
|
||||
protected:
|
||||
friend class AvatarManager;
|
||||
|
||||
|
|
|
@ -430,6 +430,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
if (!_skeletonModel->hasSkeleton()) {
|
||||
// All the simulation that can be done has been done
|
||||
getHead()->setPosition(getPosition()); // so audio-position isn't 0,0,0
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ void DialogsManager::toggleAddressBar() {
|
|||
emit addressBarToggled();
|
||||
}
|
||||
|
||||
void DialogsManager::showAddressBar() {
|
||||
AddressBarDialog::show();
|
||||
}
|
||||
|
||||
void DialogsManager::toggleDiskCacheEditor() {
|
||||
maybeCreateDialog(_diskCacheEditor);
|
||||
_diskCacheEditor->toggle();
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void toggleAddressBar();
|
||||
void showAddressBar();
|
||||
void toggleDiskCacheEditor();
|
||||
void toggleLoginDialog();
|
||||
void showLoginDialog();
|
||||
|
|
|
@ -529,7 +529,7 @@ void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const Sha
|
|||
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
|
||||
}
|
||||
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) {
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority) {
|
||||
ModelPointer model = nullptr;
|
||||
|
||||
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
||||
|
@ -543,6 +543,7 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString
|
|||
}
|
||||
|
||||
model = std::make_shared<Model>(std::make_shared<Rig>());
|
||||
model->setLoadingPriority(loadingPriority);
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||
|
|
|
@ -28,11 +28,14 @@ class AbstractViewStateInterface;
|
|||
class Model;
|
||||
class ScriptEngine;
|
||||
class ZoneEntityItem;
|
||||
class EntityItem;
|
||||
|
||||
class Model;
|
||||
using ModelPointer = std::shared_ptr<Model>;
|
||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||
|
||||
using CalculateEntityLoadingPriority = std::function<float(const EntityItem& item)>;
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public Dependency {
|
||||
Q_OBJECT
|
||||
|
@ -46,6 +49,10 @@ public:
|
|||
virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; }
|
||||
virtual void setTree(OctreePointer newTree);
|
||||
|
||||
// Returns the priority at which an entity should be loaded. Higher values indicate higher priority.
|
||||
float getEntityLoadingPriority(const EntityItem& item) const { return _calculateEntityLoadingPriorityFunc(item); }
|
||||
void setEntityLoadingPriorityFunction(CalculateEntityLoadingPriority fn) { this->_calculateEntityLoadingPriorityFunc = fn; }
|
||||
|
||||
void shutdown();
|
||||
void update();
|
||||
|
||||
|
@ -66,7 +73,7 @@ public:
|
|||
void reloadEntityScripts();
|
||||
|
||||
/// if a renderable entity item needs a model, we will allocate it for them
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, const QString& collisionUrl);
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority = 0.0f);
|
||||
|
||||
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
|
||||
Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl);
|
||||
|
@ -202,6 +209,10 @@ private:
|
|||
QList<EntityItemID> _entityIDsLastInScene;
|
||||
|
||||
static int _entitiesScriptEngineCount;
|
||||
|
||||
CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc = [](const EntityItem& item) -> float {
|
||||
return 0.0f;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -484,7 +484,7 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
|||
if (!getModelURL().isEmpty()) {
|
||||
// If we don't have a model, allocate one *immediately*
|
||||
if (!_model) {
|
||||
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL());
|
||||
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL(), renderer->getEntityLoadingPriority(*this));
|
||||
_needsInitialSimulation = true;
|
||||
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||
} else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
|
||||
|
@ -919,7 +919,9 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
|||
bool RenderableModelEntityItem::shouldBePhysical() const {
|
||||
// If we have a model, make sure it hasn't failed to download.
|
||||
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
|
||||
if (_model && _model->didGeometryRequestFail()) {
|
||||
if (_model && getShapeType() == SHAPE_TYPE_COMPOUND && _model->didCollisionGeometryRequestFail()) {
|
||||
return false;
|
||||
} else if (_model && getShapeType() != SHAPE_TYPE_NONE && _model->didVisualGeometryRequestFail()) {
|
||||
return false;
|
||||
} else {
|
||||
return ModelEntityItem::shouldBePhysical();
|
||||
|
|
|
@ -52,7 +52,6 @@ public:
|
|||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
|
||||
ModelPointer getModel(EntityTreeRenderer* renderer);
|
||||
|
||||
virtual bool needsToCallUpdate() const override;
|
||||
|
|
|
@ -403,9 +403,8 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) {
|
|||
void GeometryResourceWatcher::resourceFinished(bool success) {
|
||||
if (success) {
|
||||
_geometryRef = std::make_shared<Geometry>(*_resource);
|
||||
} else {
|
||||
emit resourceFailed();
|
||||
}
|
||||
emit finished(success);
|
||||
}
|
||||
|
||||
void GeometryResourceWatcher::resourceRefreshed() {
|
||||
|
|
|
@ -112,7 +112,7 @@ public:
|
|||
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
||||
|
||||
signals:
|
||||
void resourceFailed();
|
||||
void finished(bool success);
|
||||
|
||||
private:
|
||||
void startWatching();
|
||||
|
|
|
@ -534,13 +534,14 @@ void CharacterController::preSimulation() {
|
|||
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = _characterBodyTransform.getOrigin() - _halfHeight * _currentUp;
|
||||
btVector3 rayStart = _characterBodyTransform.getOrigin();
|
||||
|
||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
|
||||
const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||
|
@ -553,7 +554,7 @@ void CharacterController::preSimulation() {
|
|||
bool rayHasHit = rayCallback.hasHit();
|
||||
if (rayHasHit) {
|
||||
_rayHitStartTime = now;
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||
rayHasHit = true;
|
||||
} else {
|
||||
|
@ -581,7 +582,7 @@ void CharacterController::preSimulation() {
|
|||
_takeoffJumpButtonID = _jumpButtonDownCount;
|
||||
_takeoffToInAirStartTime = now;
|
||||
SET_STATE(State::Takeoff, "jump pressed");
|
||||
} else if (rayHasHit && !_hasSupport && _floorDistance > JUMP_PROXIMITY_THRESHOLD) {
|
||||
} else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) {
|
||||
SET_STATE(State::InAir, "falling");
|
||||
}
|
||||
break;
|
||||
|
@ -595,7 +596,7 @@ void CharacterController::preSimulation() {
|
|||
}
|
||||
break;
|
||||
case State::InAir: {
|
||||
if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport)) {
|
||||
if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
|
||||
SET_STATE(State::Ground, "hit ground");
|
||||
} else {
|
||||
btVector3 desiredVelocity = _targetVelocity;
|
||||
|
@ -614,7 +615,7 @@ void CharacterController::preSimulation() {
|
|||
case State::Hover:
|
||||
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
||||
SET_STATE(State::InAir, "near ground");
|
||||
} else if (((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport) && !flyingFast) {
|
||||
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
|
||||
SET_STATE(State::Ground, "touching ground");
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -111,8 +111,8 @@ Model::Model(RigPointer rig, QObject* parent) :
|
|||
|
||||
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
|
||||
|
||||
// handle download failure reported by the GeometryResourceWatcher
|
||||
connect(&_renderWatcher, &GeometryResourceWatcher::resourceFailed, this, &Model::handleGeometryResourceFailure);
|
||||
connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);
|
||||
connect(&_collisionWatcher, &GeometryResourceWatcher::finished, this, &Model::loadCollisionModelURLFinished);
|
||||
}
|
||||
|
||||
Model::~Model() {
|
||||
|
@ -822,22 +822,40 @@ void Model::setURL(const QUrl& url) {
|
|||
_needsReload = true;
|
||||
_needsUpdateTextures = true;
|
||||
_meshGroupsKnown = false;
|
||||
_geometryRequestFailed = false;
|
||||
_visualGeometryRequestFailed = false;
|
||||
invalidCalculatedMeshBoxes();
|
||||
deleteGeometry();
|
||||
|
||||
_renderWatcher.setResource(DependencyManager::get<ModelCache>()->getGeometryResource(url));
|
||||
auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(url);
|
||||
resource->setLoadPriority(this, _loadingPriority);
|
||||
_renderWatcher.setResource(resource);
|
||||
onInvalidate();
|
||||
}
|
||||
|
||||
void Model::loadURLFinished(bool success) {
|
||||
if (!success) {
|
||||
_visualGeometryRequestFailed = true;
|
||||
}
|
||||
emit setURLFinished(success);
|
||||
}
|
||||
|
||||
void Model::setCollisionModelURL(const QUrl& url) {
|
||||
if (_collisionUrl == url && _collisionWatcher.getURL() == url) {
|
||||
return;
|
||||
}
|
||||
_collisionUrl = url;
|
||||
_collisionGeometryRequestFailed = false;
|
||||
_collisionWatcher.setResource(DependencyManager::get<ModelCache>()->getGeometryResource(url));
|
||||
}
|
||||
|
||||
void Model::loadCollisionModelURLFinished(bool success) {
|
||||
if (!success) {
|
||||
_collisionGeometryRequestFailed = true;
|
||||
}
|
||||
|
||||
emit setCollisionModelURLFinished(success);
|
||||
}
|
||||
|
||||
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
|
||||
}
|
||||
|
|
|
@ -148,8 +148,9 @@ public:
|
|||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||
|
||||
bool isActive() const { return isLoaded(); }
|
||||
|
||||
bool didGeometryRequestFail() const { return _geometryRequestFailed; }
|
||||
|
||||
bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; }
|
||||
bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; }
|
||||
|
||||
bool convexHullContains(glm::vec3 point);
|
||||
|
||||
|
@ -237,6 +238,16 @@ public:
|
|||
// returns 'true' if needs fullUpdate after geometry change
|
||||
bool updateGeometry();
|
||||
|
||||
void setLoadingPriority(float priority) { _loadingPriority = priority; }
|
||||
|
||||
public slots:
|
||||
void loadURLFinished(bool success);
|
||||
void loadCollisionModelURLFinished(bool success);
|
||||
|
||||
signals:
|
||||
void setURLFinished(bool success);
|
||||
void setCollisionModelURLFinished(bool success);
|
||||
|
||||
protected:
|
||||
|
||||
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
||||
|
@ -394,10 +405,12 @@ protected:
|
|||
|
||||
uint32_t _deleteGeometryCounter { 0 };
|
||||
|
||||
bool _geometryRequestFailed { false };
|
||||
bool _visualGeometryRequestFailed { false };
|
||||
bool _collisionGeometryRequestFailed { false };
|
||||
|
||||
private:
|
||||
float _loadingPriority { 0.0f };
|
||||
|
||||
private slots:
|
||||
void handleGeometryResourceFailure() { _geometryRequestFailed = true; }
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ModelPointer)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <assert.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -109,12 +110,22 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
QUrl unnormalizedURL(scriptOrURL);
|
||||
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||
|
||||
// attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the entityScript use case)
|
||||
if (url.scheme().isEmpty() && scriptOrURL.simplified().replace(" ", "").contains("(function(){")) {
|
||||
// attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the
|
||||
// entityScript use case)
|
||||
if (unnormalizedURL.scheme().isEmpty() &&
|
||||
scriptOrURL.simplified().replace(" ", "").contains(QRegularExpression(R"(\(function\([a-z]?[\w,]*\){)"))) {
|
||||
contentAvailable(scriptOrURL, scriptOrURL, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// give a similar treatment to javacript: urls
|
||||
if (unnormalizedURL.scheme() == "javascript") {
|
||||
QString contents { scriptOrURL };
|
||||
contents.replace(QRegularExpression("^javascript:"), "");
|
||||
contentAvailable(scriptOrURL, contents, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
Lock lock(_containerLock);
|
||||
if (_scriptCache.contains(url) && !forceDownload) {
|
||||
auto scriptContent = _scriptCache[url];
|
||||
|
@ -133,6 +144,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
#endif
|
||||
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||
Q_ASSERT(request);
|
||||
request->setCacheEnabled(!forceDownload);
|
||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||
request->send();
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace Setting {
|
|||
const auto& key = handle->getKey();
|
||||
withWriteLock([&] {
|
||||
QVariant loadedValue;
|
||||
if (_pendingChanges.contains(key)) {
|
||||
if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) {
|
||||
loadedValue = _pendingChanges[key];
|
||||
} else {
|
||||
loadedValue = value(key);
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace Setting {
|
|||
private:
|
||||
QHash<QString, Interface*> _handles;
|
||||
QPointer<QTimer> _saveTimer = nullptr;
|
||||
const QVariant UNSET_VALUE { QUuid::createUuid().variant() };
|
||||
const QVariant UNSET_VALUE { QUuid::createUuid() };
|
||||
QHash<QString, QVariant> _pendingChanges;
|
||||
|
||||
friend class Interface;
|
||||
|
|
|
@ -40,7 +40,7 @@ var DEFAULT_SOUND_DATA = {
|
|||
|
||||
Script.include("../../libraries/utils.js");
|
||||
Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList.
|
||||
Avatar.skeletonModelURL = "http://invalid-url";
|
||||
Avatar.skeletonModelURL = "http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst";
|
||||
function ignore() {}
|
||||
function debug() { // Display the arguments not just [Object object].
|
||||
//print.apply(null, [].map.call(arguments, JSON.stringify));
|
||||
|
|
BIN
scripts/system/assets/models/teleport-cancel.fbx
Normal file
BIN
scripts/system/assets/models/teleport-cancel.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/models/teleport-destination.fbx
Normal file
BIN
scripts/system/assets/models/teleport-destination.fbx
Normal file
Binary file not shown.
Binary file not shown.
|
@ -107,7 +107,10 @@ var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for ne
|
|||
|
||||
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
|
||||
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
|
||||
var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds
|
||||
|
||||
// if an equipped item is "adjusted" to be too far from the hand it's in, it will be unequipped.
|
||||
var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks
|
||||
var AUTO_UNEQUIP_DISTANCE_FACTOR = 1.2; // multiplied by maximum dimension of held item, > this means drop
|
||||
|
||||
//
|
||||
// other constants
|
||||
|
@ -1267,7 +1270,7 @@ function MyController(hand) {
|
|||
if (this.triggerSmoothedGrab()) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.grabbedEntity = potentialEquipHotspot.entityID;
|
||||
this.setState(STATE_HOLD, "eqipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
|
||||
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1818,7 +1821,8 @@ function MyController(hand) {
|
|||
|
||||
this.heartBeat(this.grabbedEntity);
|
||||
|
||||
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]);
|
||||
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID",
|
||||
"position", "rotation", "dimensions"]);
|
||||
if (!props.position) {
|
||||
// server may have reset, taking our equipped entity with it. move back to "off" stte
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
|
@ -1830,10 +1834,9 @@ function MyController(hand) {
|
|||
if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) {
|
||||
this.lastUnequipCheckTime = now;
|
||||
|
||||
if (props.parentID == MyAvatar.sessionUUID &&
|
||||
Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) {
|
||||
if (props.parentID == MyAvatar.sessionUUID) {
|
||||
var handPosition = this.getHandPosition();
|
||||
// the center of the equipped object being far from the hand isn't enough to autoequip -- we also
|
||||
// the center of the equipped object being far from the hand isn't enough to auto-unequip -- we also
|
||||
// need to fail the findEntities test.
|
||||
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
||||
|
@ -2364,9 +2367,20 @@ var handleHandMessages = function(channel, message, sender) {
|
|||
try {
|
||||
data = JSON.parse(message);
|
||||
var selectedController = (data.hand === 'left') ? leftController : rightController;
|
||||
var hotspotIndex = data.hotspotIndex !== undefined ? parseInt(data.hotspotIndex) : 0;
|
||||
selectedController.release();
|
||||
var wearableEntity = data.entityID;
|
||||
entityPropertiesCache.addEntity(wearableEntity);
|
||||
selectedController.grabbedEntity = wearableEntity;
|
||||
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedEntity);
|
||||
if (hotspots.length > 0) {
|
||||
if (hotspotIndex >= hotspots.length) {
|
||||
hotspotIndex = 0;
|
||||
}
|
||||
selectedController.grabbedHotspot = hotspots[hotspotIndex];
|
||||
}
|
||||
selectedController.setState(STATE_HOLD, "Hifi-Hand-Grab msg received");
|
||||
selectedController.grabbedEntity = data.entityID;
|
||||
selectedController.nearGrabbingEnter();
|
||||
|
||||
} catch (e) {
|
||||
print("WARNING: error parsing Hifi-Hand-Grab message");
|
||||
|
@ -2405,4 +2419,4 @@ function cleanup() {
|
|||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
|
|
@ -1,34 +1,18 @@
|
|||
// Created by james b. pollack @imgntn on 7/2/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Creates a beam and target and then teleports you there.
|
||||
// Creates a beam and target and then teleports you there. Release when its close to you to cancel.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
var inTeleportMode = false;
|
||||
|
||||
// instant
|
||||
// var NUMBER_OF_STEPS = 0;
|
||||
// var SMOOTH_ARRIVAL_SPACING = 0;
|
||||
|
||||
// // slow
|
||||
// var SMOOTH_ARRIVAL_SPACING = 150;
|
||||
// var NUMBER_OF_STEPS = 2;
|
||||
|
||||
// medium-slow
|
||||
// var SMOOTH_ARRIVAL_SPACING = 100;
|
||||
// var NUMBER_OF_STEPS = 4;
|
||||
|
||||
// medium-fast
|
||||
var SMOOTH_ARRIVAL_SPACING = 33;
|
||||
var NUMBER_OF_STEPS = 6;
|
||||
|
||||
//fast
|
||||
// var SMOOTH_ARRIVAL_SPACING = 10;
|
||||
// var NUMBER_OF_STEPS = 20;
|
||||
|
||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport.fbx");
|
||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
||||
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
|
||||
var TARGET_MODEL_DIMENSIONS = {
|
||||
x: 1.15,
|
||||
y: 0.5,
|
||||
|
@ -47,7 +31,13 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
|||
blue: 141
|
||||
};
|
||||
|
||||
var MAX_AVATAR_SPEED = 0.25;
|
||||
var COLORS_TELEPORT_TOO_CLOSE = {
|
||||
red: 255,
|
||||
green: 184,
|
||||
blue: 73
|
||||
};
|
||||
|
||||
var TELEPORT_CANCEL_RANGE = 1.5;
|
||||
|
||||
function ThumbPad(hand) {
|
||||
this.hand = hand;
|
||||
|
@ -86,29 +76,17 @@ function Teleporter() {
|
|||
this.rightOverlayLine = null;
|
||||
this.leftOverlayLine = null;
|
||||
this.targetOverlay = null;
|
||||
this.cancelOverlay = null;
|
||||
this.updateConnected = null;
|
||||
this.smoothArrivalInterval = null;
|
||||
this.teleportHand = null;
|
||||
this.tooClose = false;
|
||||
|
||||
this.initialize = function() {
|
||||
this.createMappings();
|
||||
this.disableGrab();
|
||||
};
|
||||
|
||||
this.createTargetOverlay = function() {
|
||||
|
||||
if (_this.targetOverlay !== null) {
|
||||
return;
|
||||
}
|
||||
var targetOverlayProps = {
|
||||
url: TARGET_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: true
|
||||
};
|
||||
|
||||
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
|
||||
};
|
||||
|
||||
this.createMappings = function() {
|
||||
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
||||
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName);
|
||||
|
@ -143,11 +121,56 @@ function Teleporter() {
|
|||
this.updateConnected = true;
|
||||
};
|
||||
|
||||
this.createTargetOverlay = function() {
|
||||
|
||||
if (_this.targetOverlay !== null) {
|
||||
return;
|
||||
}
|
||||
var targetOverlayProps = {
|
||||
url: TARGET_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: true
|
||||
};
|
||||
|
||||
var cancelOverlayProps = {
|
||||
url: TOO_CLOSE_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: true
|
||||
};
|
||||
|
||||
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
|
||||
|
||||
};
|
||||
|
||||
this.createCancelOverlay = function() {
|
||||
|
||||
if (_this.cancelOverlay !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cancelOverlayProps = {
|
||||
url: TOO_CLOSE_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: true
|
||||
};
|
||||
|
||||
_this.cancelOverlay = Overlays.addOverlay("model", cancelOverlayProps);
|
||||
};
|
||||
|
||||
this.deleteCancelOverlay = function() {
|
||||
if (this.cancelOverlay === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Overlays.deleteOverlay(this.cancelOverlay);
|
||||
this.cancelOverlay = null;
|
||||
}
|
||||
|
||||
this.deleteTargetOverlay = function() {
|
||||
if (this.targetOverlay === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Overlays.deleteOverlay(this.targetOverlay);
|
||||
this.intersection = null;
|
||||
this.targetOverlay = null;
|
||||
|
@ -214,7 +237,7 @@ function Teleporter() {
|
|||
|
||||
var rightControllerRotation = Controller.getPoseValue(Controller.Standard.RightHand).rotation;
|
||||
|
||||
var rightRotation = Quat.multiply(MyAvatar.orientation, rightControllerRotation)
|
||||
var rightRotation = Quat.multiply(MyAvatar.orientation, rightControllerRotation);
|
||||
|
||||
var rightFinal = Quat.multiply(rightRotation, Quat.angleAxis(90, {
|
||||
x: 1,
|
||||
|
@ -235,11 +258,25 @@ function Teleporter() {
|
|||
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity]);
|
||||
|
||||
if (rightIntersection.intersects) {
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(rightIntersection);
|
||||
if (this.tooClose === true) {
|
||||
this.deleteTargetOverlay();
|
||||
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||
if (this.cancelOverlay !== null) {
|
||||
this.updateCancelOverlay(rightIntersection);
|
||||
} else {
|
||||
this.createCancelOverlay();
|
||||
}
|
||||
} else {
|
||||
this.createTargetOverlay();
|
||||
this.deleteCancelOverlay();
|
||||
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(rightIntersection);
|
||||
} else {
|
||||
this.createTargetOverlay();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -275,13 +312,27 @@ function Teleporter() {
|
|||
|
||||
if (leftIntersection.intersects) {
|
||||
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(leftIntersection);
|
||||
if (this.tooClose === true) {
|
||||
this.deleteTargetOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||
if (this.cancelOverlay !== null) {
|
||||
this.updateCancelOverlay(leftIntersection);
|
||||
} else {
|
||||
this.createCancelOverlay();
|
||||
}
|
||||
} else {
|
||||
this.createTargetOverlay();
|
||||
this.deleteCancelOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(leftIntersection);
|
||||
} else {
|
||||
this.createTargetOverlay();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
this.deleteTargetOverlay();
|
||||
|
@ -355,20 +406,44 @@ function Teleporter() {
|
|||
this.updateTargetOverlay = function(intersection) {
|
||||
_this.intersection = intersection;
|
||||
|
||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP)
|
||||
var euler = Quat.safeEulerAngles(rotation)
|
||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||
var euler = Quat.safeEulerAngles(rotation);
|
||||
var position = {
|
||||
x: intersection.intersection.x,
|
||||
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
|
||||
z: intersection.intersection.z
|
||||
}
|
||||
};
|
||||
|
||||
this.tooClose = isTooCloseToTeleport(position);
|
||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||
|
||||
Overlays.editOverlay(this.targetOverlay, {
|
||||
position: position,
|
||||
rotation: Quat.fromPitchYawRollDegrees(0, euler.y, 0),
|
||||
rotation: towardUs
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.updateCancelOverlay = function(intersection) {
|
||||
_this.intersection = intersection;
|
||||
|
||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||
var euler = Quat.safeEulerAngles(rotation);
|
||||
var position = {
|
||||
x: intersection.intersection.x,
|
||||
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
|
||||
z: intersection.intersection.z
|
||||
};
|
||||
|
||||
this.tooClose = isTooCloseToTeleport(position);
|
||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||
|
||||
Overlays.editOverlay(this.cancelOverlay, {
|
||||
position: position,
|
||||
rotation: towardUs
|
||||
});
|
||||
};
|
||||
|
||||
this.disableGrab = function() {
|
||||
Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand);
|
||||
};
|
||||
|
@ -383,10 +458,17 @@ function Teleporter() {
|
|||
};
|
||||
|
||||
this.teleport = function(value) {
|
||||
|
||||
if (value === undefined) {
|
||||
this.exitTeleportMode();
|
||||
}
|
||||
|
||||
if (this.intersection !== null) {
|
||||
if (this.tooClose === true) {
|
||||
this.exitTeleportMode();
|
||||
this.deleteCancelOverlay();
|
||||
return;
|
||||
}
|
||||
var offset = getAvatarFootOffset();
|
||||
this.intersection.intersection.y += offset;
|
||||
this.exitTeleportMode();
|
||||
|
@ -394,7 +476,6 @@ function Teleporter() {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
this.findMidpoint = function(start, end) {
|
||||
var xy = Vec3.sum(start, end);
|
||||
var midpoint = Vec3.multiply(0.5, xy);
|
||||
|
@ -433,13 +514,13 @@ function Teleporter() {
|
|||
|
||||
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
|
||||
_this.deleteTargetOverlay();
|
||||
_this.deleteCancelOverlay();
|
||||
}
|
||||
|
||||
}, SMOOTH_ARRIVAL_SPACING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//related to repositioning the avatar after you teleport
|
||||
function getAvatarFootOffset() {
|
||||
var data = getJointData();
|
||||
|
@ -505,7 +586,11 @@ function isMoving() {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function isTooCloseToTeleport(position) {
|
||||
return Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE;
|
||||
};
|
||||
|
||||
function registerMappings() {
|
||||
mappingName = 'Hifi-Teleporter-Dev-' + Math.random();
|
||||
|
@ -559,7 +644,7 @@ function registerMappings() {
|
|||
}, TELEPORT_DELAY)
|
||||
return;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registerMappings();
|
||||
|
||||
|
@ -573,6 +658,7 @@ function cleanup() {
|
|||
teleportMapping.disable();
|
||||
teleporter.disableMappings();
|
||||
teleporter.deleteTargetOverlay();
|
||||
teleporter.deleteCancelOverlay();
|
||||
teleporter.turnOffOverlayBeams();
|
||||
if (teleporter.updateConnected !== null) {
|
||||
Script.update.disconnect(teleporter.update);
|
||||
|
|
Loading…
Reference in a new issue