mirror of
https://github.com/overte-org/overte.git
synced 2025-08-11 05:53:07 +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 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,
|
float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
||||||
const AvatarAudioStream& listeningNodeStream, const glm::vec3& relativePosition, bool isEcho) {
|
const AvatarAudioStream& listeningNodeStream, const glm::vec3& relativePosition, bool isEcho) {
|
||||||
float gain = 1.0f;
|
float gain = 1.0f;
|
||||||
|
@ -148,7 +205,7 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
||||||
g = (g > 1.0f) ? 1.0f : g;
|
g = (g > 1.0f) ? 1.0f : g;
|
||||||
|
|
||||||
// calculate the distance coefficient using the distance to this node
|
// 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
|
// multiply the current attenuation coefficient by the distance coefficient
|
||||||
gain *= distanceCoefficient;
|
gain *= distanceCoefficient;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<ul class="nav nav-pills nav-stacked">
|
||||||
</ul>
|
</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>
|
<button class="btn btn-success save-button">Save and restart</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -182,7 +182,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
|
||||||
|
|
||||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||||
#ifdef WANT_DEBUG
|
#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;
|
<< rank.name << "so:" << userPerms;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,9 +117,18 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
_settingsManager.apiRefreshGroupInformation();
|
_settingsManager.apiRefreshGroupInformation();
|
||||||
|
|
||||||
setupNodeListAndAssignments();
|
setupNodeListAndAssignments();
|
||||||
|
|
||||||
|
if (_type == MetaverseDomain) {
|
||||||
|
// if we have a metaverse domain, we'll need an access token to heartbeat handle auto-networking
|
||||||
|
resetAccountManagerAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
setupAutomaticNetworking();
|
setupAutomaticNetworking();
|
||||||
if (!getID().isNull()) {
|
|
||||||
|
if (!getID().isNull() && _type != NonMetaverse) {
|
||||||
|
// setup periodic heartbeats to metaverse API
|
||||||
setupHeartbeatToMetaverse();
|
setupHeartbeatToMetaverse();
|
||||||
|
|
||||||
// send the first heartbeat immediately
|
// send the first heartbeat immediately
|
||||||
sendHeartbeatToMetaverse();
|
sendHeartbeatToMetaverse();
|
||||||
}
|
}
|
||||||
|
@ -301,16 +310,22 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
// store the new ID and auto networking setting on disk
|
// store the new ID and auto networking setting on disk
|
||||||
_settingsManager.persistToFile();
|
_settingsManager.persistToFile();
|
||||||
|
|
||||||
// change our domain ID immediately
|
|
||||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
|
||||||
|
|
||||||
// store the new token to the account info
|
// store the new token to the account info
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
accountManager->setTemporaryDomain(id, key);
|
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
|
// update our heartbeats to use the correct id
|
||||||
setupICEHeartbeatForFullNetworking();
|
setupICEHeartbeatForFullNetworking();
|
||||||
setupHeartbeatToMetaverse();
|
setupHeartbeatToMetaverse();
|
||||||
|
|
||||||
|
// if we have a current ICE server address, update it in the API for the new temporary domain
|
||||||
|
sendICEServerAddressToMetaverseAPI();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
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.";
|
<< "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);
|
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||||
if (idValueVariant) {
|
if (idValueVariant) {
|
||||||
nodeList->setSessionUUID(idValueVariant->toString());
|
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 {
|
} else {
|
||||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||||
}
|
}
|
||||||
|
@ -477,42 +502,46 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::setupAutomaticNetworking() {
|
void DomainServer::setupAutomaticNetworking() {
|
||||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
|
||||||
|
|
||||||
resetAccountManagerAccessToken();
|
|
||||||
|
|
||||||
_automaticNetworkingSetting =
|
_automaticNetworkingSetting =
|
||||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
qDebug() << "Configuring automatic networking in domain-server as" << _automaticNetworkingSetting;
|
||||||
const QUuid& domainID = getID();
|
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
if (_automaticNetworkingSetting != DISABLED_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
setupICEHeartbeatForFullNetworking();
|
const QUuid& domainID = getID();
|
||||||
}
|
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
setupICEHeartbeatForFullNetworking();
|
||||||
|
}
|
||||||
|
|
||||||
if (!domainID.isNull()) {
|
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
if (!domainID.isNull()) {
|
||||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||||
this, &DomainServer::performIPAddressUpdate);
|
|
||||||
|
|
||||||
// have the LNL enable public socket updating via STUN
|
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
nodeList->startSTUNPublicSocketUpdate();
|
|
||||||
|
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() {
|
void DomainServer::setupHeartbeatToMetaverse() {
|
||||||
|
@ -1139,42 +1168,45 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to force a new temporary domain name
|
// only attempt to grab a new temporary name if we're already a temporary domain server
|
||||||
switch (requestReply.error()) {
|
if (_type == MetaverseTemporaryDomain) {
|
||||||
// if we have a temporary domain with a bad token, we get a 401
|
// check if we need to force a new temporary domain name
|
||||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
switch (requestReply.error()) {
|
||||||
static const QString DATA_KEY = "data";
|
// if we have a temporary domain with a bad token, we get a 401
|
||||||
static const QString TOKEN_KEY = "api_key";
|
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||||
|
static const QString DATA_KEY = "data";
|
||||||
|
static const QString TOKEN_KEY = "api_key";
|
||||||
|
|
||||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||||
|
|
||||||
if (!tokenFailure.isNull()) {
|
if (!tokenFailure.isNull()) {
|
||||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
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
|
// halt heartbeats until we have a token
|
||||||
_metaverseHeartbeatTimer->deleteLater();
|
_metaverseHeartbeatTimer->deleteLater();
|
||||||
_metaverseHeartbeatTimer = nullptr;
|
_metaverseHeartbeatTimer = nullptr;
|
||||||
|
|
||||||
// give up eventually to avoid flooding traffic
|
// give up eventually to avoid flooding traffic
|
||||||
static const int MAX_ATTEMPTS = 5;
|
static const int MAX_ATTEMPTS = 5;
|
||||||
static int attempt = 0;
|
static int attempt = 0;
|
||||||
if (++attempt < MAX_ATTEMPTS) {
|
if (++attempt < MAX_ATTEMPTS) {
|
||||||
// get a new temporary name and token
|
// get a new temporary name and token
|
||||||
getTemporaryName(true);
|
getTemporaryName(true);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
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(int argc, char* argv[]);
|
||||||
~DomainServer();
|
~DomainServer();
|
||||||
|
|
||||||
|
enum DomainType {
|
||||||
|
NonMetaverse,
|
||||||
|
MetaverseDomain,
|
||||||
|
MetaverseTemporaryDomain
|
||||||
|
};
|
||||||
|
|
||||||
static int const EXIT_CODE_REBOOT;
|
static int const EXIT_CODE_REBOOT;
|
||||||
|
|
||||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||||
|
@ -195,6 +201,8 @@ private:
|
||||||
int _numHeartbeatDenials { 0 };
|
int _numHeartbeatDenials { 0 };
|
||||||
bool _connectedToICEServer { false };
|
bool _connectedToICEServer { false };
|
||||||
|
|
||||||
|
DomainType _type { DomainType::NonMetaverse };
|
||||||
|
|
||||||
friend class DomainGatekeeper;
|
friend class DomainGatekeeper;
|
||||||
friend class DomainMetadata;
|
friend class DomainMetadata;
|
||||||
};
|
};
|
||||||
|
|
|
@ -356,7 +356,7 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap&
|
||||||
if (nameKey.first.toLower() != groupNameLower) {
|
if (nameKey.first.toLower() != groupNameLower) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QUuid groupID = _groupIDs[groupNameLower];
|
QUuid groupID = _groupIDs[groupNameLower.toLower()];
|
||||||
QUuid rankID = nameKey.second;
|
QUuid rankID = nameKey.second;
|
||||||
GroupRank rank = _groupRanks[groupID][rankID];
|
GroupRank rank = _groupRanks[groupID][rankID];
|
||||||
if (rank.order == 0) {
|
if (rank.order == 0) {
|
||||||
|
@ -1477,14 +1477,14 @@ void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& r
|
||||||
|
|
||||||
void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) {
|
void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) {
|
||||||
if (rankID != QUuid()) {
|
if (rankID != QUuid()) {
|
||||||
_groupMembership[name][groupID] = rankID;
|
_groupMembership[name.toLower()][groupID] = rankID;
|
||||||
} else {
|
} else {
|
||||||
_groupMembership[name].remove(groupID);
|
_groupMembership[name.toLower()].remove(groupID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& 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)) {
|
if (groupsForName.contains(groupID)) {
|
||||||
return groupsForName[groupID];
|
return groupsForName[groupID];
|
||||||
}
|
}
|
||||||
|
@ -1528,7 +1528,7 @@ void DomainServerSettingsManager::debugDumpGroupsState() {
|
||||||
|
|
||||||
qDebug() << "_groupIDs:";
|
qDebug() << "_groupIDs:";
|
||||||
foreach (QString groupName, _groupIDs.keys()) {
|
foreach (QString groupName, _groupIDs.keys()) {
|
||||||
qDebug() << "| " << groupName << "==>" << _groupIDs[groupName];
|
qDebug() << "| " << groupName << "==>" << _groupIDs[groupName.toLower()];
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "_groupNames:";
|
qDebug() << "_groupNames:";
|
||||||
|
@ -1548,7 +1548,7 @@ void DomainServerSettingsManager::debugDumpGroupsState() {
|
||||||
|
|
||||||
qDebug() << "_groupMembership";
|
qDebug() << "_groupMembership";
|
||||||
foreach (QString userName, _groupMembership.keys()) {
|
foreach (QString userName, _groupMembership.keys()) {
|
||||||
QHash<QUuid, QUuid>& groupsForUser = _groupMembership[userName];
|
QHash<QUuid, QUuid>& groupsForUser = _groupMembership[userName.toLower()];
|
||||||
QString line = "";
|
QString line = "";
|
||||||
foreach (QUuid groupID, groupsForUser.keys()) {
|
foreach (QUuid groupID, groupsForUser.keys()) {
|
||||||
line += " g=" + groupID.toString() + ",r=" + groupsForUser[groupID].toString();
|
line += " g=" + groupID.toString() + ",r=" + groupsForUser[groupID].toString();
|
||||||
|
|
|
@ -84,7 +84,7 @@ public:
|
||||||
QList<QUuid> getBlacklistGroupIDs();
|
QList<QUuid> getBlacklistGroupIDs();
|
||||||
|
|
||||||
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
|
// 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);
|
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
|
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;
|
property var uploadOpen: false;
|
||||||
Timer {
|
Timer {
|
||||||
|
@ -366,6 +374,10 @@ ScrollingWindow {
|
||||||
}, dropping);
|
}, dropping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initiateUpload(url) {
|
||||||
|
doUpload(doUploadTimer.url, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (fileUrl) {
|
if (fileUrl) {
|
||||||
doUpload(fileUrl, true);
|
doUpload(fileUrl, true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -373,12 +385,21 @@ ScrollingWindow {
|
||||||
selectDirectory: false,
|
selectDirectory: false,
|
||||||
dir: currentDirectory
|
dir: currentDirectory
|
||||||
});
|
});
|
||||||
|
|
||||||
browser.canceled.connect(function() {
|
browser.canceled.connect(function() {
|
||||||
uploadOpen = false;
|
uploadOpen = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
browser.selectedFile.connect(function(url) {
|
browser.selectedFile.connect(function(url) {
|
||||||
currentDirectory = browser.dir;
|
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);
|
connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket);
|
||||||
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
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();
|
_glWidget = new GLCanvas();
|
||||||
getApplicationCompositor().setRenderingWidget(_glWidget);
|
getApplicationCompositor().setRenderingWidget(_glWidget);
|
||||||
|
@ -811,6 +818,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
}
|
}
|
||||||
UserActivityLogger::getInstance().logAction("launch", properties);
|
UserActivityLogger::getInstance().logAction("launch", properties);
|
||||||
|
|
||||||
|
_connectionMonitor.init();
|
||||||
|
|
||||||
// Tell our entity edit sender about our known jurisdictions
|
// Tell our entity edit sender about our known jurisdictions
|
||||||
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
|
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
|
||||||
|
@ -3240,6 +3248,18 @@ void Application::init() {
|
||||||
getEntities()->setViewFrustum(_viewFrustum);
|
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);
|
ObjectMotionState::setShapeManager(&_shapeManager);
|
||||||
_physicsEngine->init();
|
_physicsEngine->init();
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
#include "avatar/MyAvatar.h"
|
#include "avatar/MyAvatar.h"
|
||||||
#include "Bookmarks.h"
|
#include "Bookmarks.h"
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
|
#include "ConnectionMonitor.h"
|
||||||
#include "FileLogger.h"
|
#include "FileLogger.h"
|
||||||
#include "gpu/Context.h"
|
#include "gpu/Context.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
|
@ -562,6 +563,9 @@ private:
|
||||||
bool _recentlyClearedDomain { false };
|
bool _recentlyClearedDomain { false };
|
||||||
|
|
||||||
QString _returnFromFullScreenMirrorTo;
|
QString _returnFromFullScreenMirrorTo;
|
||||||
|
|
||||||
|
ConnectionMonitor _connectionMonitor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif // hifi_Application_h
|
#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));
|
_headData = static_cast<HeadData*>(new Head(this));
|
||||||
|
|
||||||
_skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);
|
_skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);
|
||||||
|
connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar::~Avatar() {
|
Avatar::~Avatar() {
|
||||||
|
@ -298,7 +299,9 @@ void Avatar::simulate(float deltaTime) {
|
||||||
{
|
{
|
||||||
PerformanceTimer perfTimer("head");
|
PerformanceTimer perfTimer("head");
|
||||||
glm::vec3 headPosition = getPosition();
|
glm::vec3 headPosition = getPosition();
|
||||||
_skeletonModel->getHeadPosition(headPosition);
|
if (!_skeletonModel->getHeadPosition(headPosition)) {
|
||||||
|
headPosition = getPosition();
|
||||||
|
}
|
||||||
Head* head = getHead();
|
Head* head = getHead();
|
||||||
head->setPosition(headPosition);
|
head->setPosition(headPosition);
|
||||||
head->setScale(getUniformScale());
|
head->setScale(getUniformScale());
|
||||||
|
@ -306,6 +309,7 @@ void Avatar::simulate(float deltaTime) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
// 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);
|
_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
|
// create new model, can return an instance of a SoftAttachmentModel rather then Model
|
||||||
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
|
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
|
||||||
if (isSoft) {
|
if (isSoft) {
|
||||||
|
|
|
@ -184,6 +184,8 @@ public slots:
|
||||||
glm::vec3 getRightPalmPosition() const;
|
glm::vec3 getRightPalmPosition() const;
|
||||||
glm::quat getRightPalmRotation() const;
|
glm::quat getRightPalmRotation() const;
|
||||||
|
|
||||||
|
void setModelURLFinished(bool success);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class AvatarManager;
|
friend class AvatarManager;
|
||||||
|
|
||||||
|
|
|
@ -430,6 +430,7 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
|
|
||||||
if (!_skeletonModel->hasSkeleton()) {
|
if (!_skeletonModel->hasSkeleton()) {
|
||||||
// All the simulation that can be done has been done
|
// All the simulation that can be done has been done
|
||||||
|
getHead()->setPosition(getPosition()); // so audio-position isn't 0,0,0
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,10 @@ void DialogsManager::toggleAddressBar() {
|
||||||
emit addressBarToggled();
|
emit addressBarToggled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DialogsManager::showAddressBar() {
|
||||||
|
AddressBarDialog::show();
|
||||||
|
}
|
||||||
|
|
||||||
void DialogsManager::toggleDiskCacheEditor() {
|
void DialogsManager::toggleDiskCacheEditor() {
|
||||||
maybeCreateDialog(_diskCacheEditor);
|
maybeCreateDialog(_diskCacheEditor);
|
||||||
_diskCacheEditor->toggle();
|
_diskCacheEditor->toggle();
|
||||||
|
|
|
@ -44,6 +44,7 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleAddressBar();
|
void toggleAddressBar();
|
||||||
|
void showAddressBar();
|
||||||
void toggleDiskCacheEditor();
|
void toggleDiskCacheEditor();
|
||||||
void toggleLoginDialog();
|
void toggleLoginDialog();
|
||||||
void showLoginDialog();
|
void showLoginDialog();
|
||||||
|
|
|
@ -529,7 +529,7 @@ void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const Sha
|
||||||
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
|
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;
|
ModelPointer model = nullptr;
|
||||||
|
|
||||||
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
// 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 = std::make_shared<Model>(std::make_shared<Rig>());
|
||||||
|
model->setLoadingPriority(loadingPriority);
|
||||||
model->init();
|
model->init();
|
||||||
model->setURL(QUrl(url));
|
model->setURL(QUrl(url));
|
||||||
model->setCollisionModelURL(QUrl(collisionUrl));
|
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||||
|
|
|
@ -28,11 +28,14 @@ class AbstractViewStateInterface;
|
||||||
class Model;
|
class Model;
|
||||||
class ScriptEngine;
|
class ScriptEngine;
|
||||||
class ZoneEntityItem;
|
class ZoneEntityItem;
|
||||||
|
class EntityItem;
|
||||||
|
|
||||||
class Model;
|
class Model;
|
||||||
using ModelPointer = std::shared_ptr<Model>;
|
using ModelPointer = std::shared_ptr<Model>;
|
||||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||||
|
|
||||||
|
using CalculateEntityLoadingPriority = std::function<float(const EntityItem& item)>;
|
||||||
|
|
||||||
// Generic client side Octree renderer class.
|
// Generic client side Octree renderer class.
|
||||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public Dependency {
|
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -46,6 +49,10 @@ public:
|
||||||
virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; }
|
virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; }
|
||||||
virtual void setTree(OctreePointer newTree);
|
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 shutdown();
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
|
@ -66,7 +73,7 @@ public:
|
||||||
void reloadEntityScripts();
|
void reloadEntityScripts();
|
||||||
|
|
||||||
/// 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 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
|
/// 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);
|
Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl);
|
||||||
|
@ -202,6 +209,10 @@ private:
|
||||||
QList<EntityItemID> _entityIDsLastInScene;
|
QList<EntityItemID> _entityIDsLastInScene;
|
||||||
|
|
||||||
static int _entitiesScriptEngineCount;
|
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 (!getModelURL().isEmpty()) {
|
||||||
// If we don't have a model, allocate one *immediately*
|
// If we don't have a model, allocate one *immediately*
|
||||||
if (!_model) {
|
if (!_model) {
|
||||||
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL());
|
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL(), renderer->getEntityLoadingPriority(*this));
|
||||||
_needsInitialSimulation = true;
|
_needsInitialSimulation = true;
|
||||||
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||||
} else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
|
} 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 {
|
bool RenderableModelEntityItem::shouldBePhysical() const {
|
||||||
// If we have a model, make sure it hasn't failed to download.
|
// 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 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;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return ModelEntityItem::shouldBePhysical();
|
return ModelEntityItem::shouldBePhysical();
|
||||||
|
|
|
@ -52,7 +52,6 @@ public:
|
||||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal,
|
BoxFace& face, glm::vec3& surfaceNormal,
|
||||||
void** intersectedObject, bool precisionPicking) const override;
|
void** intersectedObject, bool precisionPicking) const override;
|
||||||
|
|
||||||
ModelPointer getModel(EntityTreeRenderer* renderer);
|
ModelPointer getModel(EntityTreeRenderer* renderer);
|
||||||
|
|
||||||
virtual bool needsToCallUpdate() const override;
|
virtual bool needsToCallUpdate() const override;
|
||||||
|
|
|
@ -403,9 +403,8 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) {
|
||||||
void GeometryResourceWatcher::resourceFinished(bool success) {
|
void GeometryResourceWatcher::resourceFinished(bool success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
_geometryRef = std::make_shared<Geometry>(*_resource);
|
_geometryRef = std::make_shared<Geometry>(*_resource);
|
||||||
} else {
|
|
||||||
emit resourceFailed();
|
|
||||||
}
|
}
|
||||||
|
emit finished(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeometryResourceWatcher::resourceRefreshed() {
|
void GeometryResourceWatcher::resourceRefreshed() {
|
||||||
|
|
|
@ -112,7 +112,7 @@ public:
|
||||||
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void resourceFailed();
|
void finished(bool success);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startWatching();
|
void startWatching();
|
||||||
|
|
|
@ -534,13 +534,14 @@ void CharacterController::preSimulation() {
|
||||||
|
|
||||||
// scan for distant floor
|
// scan for distant floor
|
||||||
// rayStart is at center of bottom sphere
|
// rayStart is at center of bottom sphere
|
||||||
btVector3 rayStart = _characterBodyTransform.getOrigin() - _halfHeight * _currentUp;
|
btVector3 rayStart = _characterBodyTransform.getOrigin();
|
||||||
|
|
||||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
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 quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||||
|
@ -553,7 +554,7 @@ void CharacterController::preSimulation() {
|
||||||
bool rayHasHit = rayCallback.hasHit();
|
bool rayHasHit = rayCallback.hasHit();
|
||||||
if (rayHasHit) {
|
if (rayHasHit) {
|
||||||
_rayHitStartTime = now;
|
_rayHitStartTime = now;
|
||||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||||
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||||
rayHasHit = true;
|
rayHasHit = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -581,7 +582,7 @@ void CharacterController::preSimulation() {
|
||||||
_takeoffJumpButtonID = _jumpButtonDownCount;
|
_takeoffJumpButtonID = _jumpButtonDownCount;
|
||||||
_takeoffToInAirStartTime = now;
|
_takeoffToInAirStartTime = now;
|
||||||
SET_STATE(State::Takeoff, "jump pressed");
|
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");
|
SET_STATE(State::InAir, "falling");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -595,7 +596,7 @@ void CharacterController::preSimulation() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case State::InAir: {
|
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");
|
SET_STATE(State::Ground, "hit ground");
|
||||||
} else {
|
} else {
|
||||||
btVector3 desiredVelocity = _targetVelocity;
|
btVector3 desiredVelocity = _targetVelocity;
|
||||||
|
@ -614,7 +615,7 @@ void CharacterController::preSimulation() {
|
||||||
case State::Hover:
|
case State::Hover:
|
||||||
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
||||||
SET_STATE(State::InAir, "near ground");
|
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");
|
SET_STATE(State::Ground, "touching ground");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -111,8 +111,8 @@ Model::Model(RigPointer rig, QObject* parent) :
|
||||||
|
|
||||||
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
|
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
|
||||||
|
|
||||||
// handle download failure reported by the GeometryResourceWatcher
|
connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);
|
||||||
connect(&_renderWatcher, &GeometryResourceWatcher::resourceFailed, this, &Model::handleGeometryResourceFailure);
|
connect(&_collisionWatcher, &GeometryResourceWatcher::finished, this, &Model::loadCollisionModelURLFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
Model::~Model() {
|
Model::~Model() {
|
||||||
|
@ -822,22 +822,40 @@ void Model::setURL(const QUrl& url) {
|
||||||
_needsReload = true;
|
_needsReload = true;
|
||||||
_needsUpdateTextures = true;
|
_needsUpdateTextures = true;
|
||||||
_meshGroupsKnown = false;
|
_meshGroupsKnown = false;
|
||||||
_geometryRequestFailed = false;
|
_visualGeometryRequestFailed = false;
|
||||||
invalidCalculatedMeshBoxes();
|
invalidCalculatedMeshBoxes();
|
||||||
deleteGeometry();
|
deleteGeometry();
|
||||||
|
|
||||||
_renderWatcher.setResource(DependencyManager::get<ModelCache>()->getGeometryResource(url));
|
auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(url);
|
||||||
|
resource->setLoadPriority(this, _loadingPriority);
|
||||||
|
_renderWatcher.setResource(resource);
|
||||||
onInvalidate();
|
onInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Model::loadURLFinished(bool success) {
|
||||||
|
if (!success) {
|
||||||
|
_visualGeometryRequestFailed = true;
|
||||||
|
}
|
||||||
|
emit setURLFinished(success);
|
||||||
|
}
|
||||||
|
|
||||||
void Model::setCollisionModelURL(const QUrl& url) {
|
void Model::setCollisionModelURL(const QUrl& url) {
|
||||||
if (_collisionUrl == url && _collisionWatcher.getURL() == url) {
|
if (_collisionUrl == url && _collisionWatcher.getURL() == url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_collisionUrl = url;
|
_collisionUrl = url;
|
||||||
|
_collisionGeometryRequestFailed = false;
|
||||||
_collisionWatcher.setResource(DependencyManager::get<ModelCache>()->getGeometryResource(url));
|
_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 {
|
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||||
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
|
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,8 +148,9 @@ public:
|
||||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||||
|
|
||||||
bool isActive() const { return isLoaded(); }
|
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);
|
bool convexHullContains(glm::vec3 point);
|
||||||
|
|
||||||
|
@ -237,6 +238,16 @@ public:
|
||||||
// returns 'true' if needs fullUpdate after geometry change
|
// returns 'true' if needs fullUpdate after geometry change
|
||||||
bool updateGeometry();
|
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:
|
protected:
|
||||||
|
|
||||||
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
||||||
|
@ -394,10 +405,12 @@ protected:
|
||||||
|
|
||||||
uint32_t _deleteGeometryCounter { 0 };
|
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)
|
Q_DECLARE_METATYPE(ModelPointer)
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
@ -109,12 +110,22 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
||||||
QUrl unnormalizedURL(scriptOrURL);
|
QUrl unnormalizedURL(scriptOrURL);
|
||||||
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
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)
|
// attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the
|
||||||
if (url.scheme().isEmpty() && scriptOrURL.simplified().replace(" ", "").contains("(function(){")) {
|
// entityScript use case)
|
||||||
|
if (unnormalizedURL.scheme().isEmpty() &&
|
||||||
|
scriptOrURL.simplified().replace(" ", "").contains(QRegularExpression(R"(\(function\([a-z]?[\w,]*\){)"))) {
|
||||||
contentAvailable(scriptOrURL, scriptOrURL, false, true);
|
contentAvailable(scriptOrURL, scriptOrURL, false, true);
|
||||||
return;
|
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);
|
Lock lock(_containerLock);
|
||||||
if (_scriptCache.contains(url) && !forceDownload) {
|
if (_scriptCache.contains(url) && !forceDownload) {
|
||||||
auto scriptContent = _scriptCache[url];
|
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() << "]";
|
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
#endif
|
#endif
|
||||||
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||||
|
Q_ASSERT(request);
|
||||||
request->setCacheEnabled(!forceDownload);
|
request->setCacheEnabled(!forceDownload);
|
||||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||||
request->send();
|
request->send();
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace Setting {
|
||||||
const auto& key = handle->getKey();
|
const auto& key = handle->getKey();
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
QVariant loadedValue;
|
QVariant loadedValue;
|
||||||
if (_pendingChanges.contains(key)) {
|
if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) {
|
||||||
loadedValue = _pendingChanges[key];
|
loadedValue = _pendingChanges[key];
|
||||||
} else {
|
} else {
|
||||||
loadedValue = value(key);
|
loadedValue = value(key);
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace Setting {
|
||||||
private:
|
private:
|
||||||
QHash<QString, Interface*> _handles;
|
QHash<QString, Interface*> _handles;
|
||||||
QPointer<QTimer> _saveTimer = nullptr;
|
QPointer<QTimer> _saveTimer = nullptr;
|
||||||
const QVariant UNSET_VALUE { QUuid::createUuid().variant() };
|
const QVariant UNSET_VALUE { QUuid::createUuid() };
|
||||||
QHash<QString, QVariant> _pendingChanges;
|
QHash<QString, QVariant> _pendingChanges;
|
||||||
|
|
||||||
friend class Interface;
|
friend class Interface;
|
||||||
|
|
|
@ -40,7 +40,7 @@ var DEFAULT_SOUND_DATA = {
|
||||||
|
|
||||||
Script.include("../../libraries/utils.js");
|
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.
|
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 ignore() {}
|
||||||
function debug() { // Display the arguments not just [Object object].
|
function debug() { // Display the arguments not just [Object object].
|
||||||
//print.apply(null, [].map.call(arguments, JSON.stringify));
|
//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 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 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
|
// other constants
|
||||||
|
@ -1267,7 +1270,7 @@ function MyController(hand) {
|
||||||
if (this.triggerSmoothedGrab()) {
|
if (this.triggerSmoothedGrab()) {
|
||||||
this.grabbedHotspot = potentialEquipHotspot;
|
this.grabbedHotspot = potentialEquipHotspot;
|
||||||
this.grabbedEntity = potentialEquipHotspot.entityID;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1818,7 +1821,8 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.heartBeat(this.grabbedEntity);
|
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) {
|
if (!props.position) {
|
||||||
// server may have reset, taking our equipped entity with it. move back to "off" stte
|
// server may have reset, taking our equipped entity with it. move back to "off" stte
|
||||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||||
|
@ -1830,10 +1834,9 @@ function MyController(hand) {
|
||||||
if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) {
|
if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) {
|
||||||
this.lastUnequipCheckTime = now;
|
this.lastUnequipCheckTime = now;
|
||||||
|
|
||||||
if (props.parentID == MyAvatar.sessionUUID &&
|
if (props.parentID == MyAvatar.sessionUUID) {
|
||||||
Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) {
|
|
||||||
var handPosition = this.getHandPosition();
|
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.
|
// need to fail the findEntities test.
|
||||||
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||||
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
||||||
|
@ -2364,9 +2367,20 @@ var handleHandMessages = function(channel, message, sender) {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(message);
|
data = JSON.parse(message);
|
||||||
var selectedController = (data.hand === 'left') ? leftController : rightController;
|
var selectedController = (data.hand === 'left') ? leftController : rightController;
|
||||||
|
var hotspotIndex = data.hotspotIndex !== undefined ? parseInt(data.hotspotIndex) : 0;
|
||||||
selectedController.release();
|
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.setState(STATE_HOLD, "Hifi-Hand-Grab msg received");
|
||||||
selectedController.grabbedEntity = data.entityID;
|
selectedController.nearGrabbingEnter();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("WARNING: error parsing Hifi-Hand-Grab message");
|
print("WARNING: error parsing Hifi-Hand-Grab message");
|
||||||
|
@ -2405,4 +2419,4 @@ function cleanup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.scriptEnding.connect(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
|
// Created by james b. pollack @imgntn on 7/2/2016
|
||||||
// Copyright 2016 High Fidelity, Inc.
|
// 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.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
var inTeleportMode = false;
|
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 SMOOTH_ARRIVAL_SPACING = 33;
|
||||||
var NUMBER_OF_STEPS = 6;
|
var NUMBER_OF_STEPS = 6;
|
||||||
|
|
||||||
//fast
|
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
||||||
// var SMOOTH_ARRIVAL_SPACING = 10;
|
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
|
||||||
// var NUMBER_OF_STEPS = 20;
|
|
||||||
|
|
||||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport.fbx");
|
|
||||||
var TARGET_MODEL_DIMENSIONS = {
|
var TARGET_MODEL_DIMENSIONS = {
|
||||||
x: 1.15,
|
x: 1.15,
|
||||||
y: 0.5,
|
y: 0.5,
|
||||||
|
@ -47,7 +31,13 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
||||||
blue: 141
|
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) {
|
function ThumbPad(hand) {
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
|
@ -86,29 +76,17 @@ function Teleporter() {
|
||||||
this.rightOverlayLine = null;
|
this.rightOverlayLine = null;
|
||||||
this.leftOverlayLine = null;
|
this.leftOverlayLine = null;
|
||||||
this.targetOverlay = null;
|
this.targetOverlay = null;
|
||||||
|
this.cancelOverlay = null;
|
||||||
this.updateConnected = null;
|
this.updateConnected = null;
|
||||||
this.smoothArrivalInterval = null;
|
this.smoothArrivalInterval = null;
|
||||||
this.teleportHand = null;
|
this.teleportHand = null;
|
||||||
|
this.tooClose = false;
|
||||||
|
|
||||||
this.initialize = function() {
|
this.initialize = function() {
|
||||||
this.createMappings();
|
this.createMappings();
|
||||||
this.disableGrab();
|
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() {
|
this.createMappings = function() {
|
||||||
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
||||||
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName);
|
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName);
|
||||||
|
@ -143,11 +121,56 @@ function Teleporter() {
|
||||||
this.updateConnected = true;
|
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() {
|
this.deleteTargetOverlay = function() {
|
||||||
if (this.targetOverlay === null) {
|
if (this.targetOverlay === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Overlays.deleteOverlay(this.targetOverlay);
|
Overlays.deleteOverlay(this.targetOverlay);
|
||||||
this.intersection = null;
|
this.intersection = null;
|
||||||
this.targetOverlay = null;
|
this.targetOverlay = null;
|
||||||
|
@ -214,7 +237,7 @@ function Teleporter() {
|
||||||
|
|
||||||
var rightControllerRotation = Controller.getPoseValue(Controller.Standard.RightHand).rotation;
|
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, {
|
var rightFinal = Quat.multiply(rightRotation, Quat.angleAxis(90, {
|
||||||
x: 1,
|
x: 1,
|
||||||
|
@ -235,11 +258,25 @@ function Teleporter() {
|
||||||
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity]);
|
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity]);
|
||||||
|
|
||||||
if (rightIntersection.intersects) {
|
if (rightIntersection.intersects) {
|
||||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
if (this.tooClose === true) {
|
||||||
if (this.targetOverlay !== null) {
|
this.deleteTargetOverlay();
|
||||||
this.updateTargetOverlay(rightIntersection);
|
|
||||||
|
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||||
|
if (this.cancelOverlay !== null) {
|
||||||
|
this.updateCancelOverlay(rightIntersection);
|
||||||
|
} else {
|
||||||
|
this.createCancelOverlay();
|
||||||
|
}
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
|
@ -275,13 +312,27 @@ function Teleporter() {
|
||||||
|
|
||||||
if (leftIntersection.intersects) {
|
if (leftIntersection.intersects) {
|
||||||
|
|
||||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
if (this.tooClose === true) {
|
||||||
if (this.targetOverlay !== null) {
|
this.deleteTargetOverlay();
|
||||||
this.updateTargetOverlay(leftIntersection);
|
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||||
|
if (this.cancelOverlay !== null) {
|
||||||
|
this.updateCancelOverlay(leftIntersection);
|
||||||
|
} else {
|
||||||
|
this.createCancelOverlay();
|
||||||
|
}
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
|
|
||||||
this.deleteTargetOverlay();
|
this.deleteTargetOverlay();
|
||||||
|
@ -355,20 +406,44 @@ function Teleporter() {
|
||||||
this.updateTargetOverlay = function(intersection) {
|
this.updateTargetOverlay = function(intersection) {
|
||||||
_this.intersection = intersection;
|
_this.intersection = intersection;
|
||||||
|
|
||||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP)
|
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||||
var euler = Quat.safeEulerAngles(rotation)
|
var euler = Quat.safeEulerAngles(rotation);
|
||||||
var position = {
|
var position = {
|
||||||
x: intersection.intersection.x,
|
x: intersection.intersection.x,
|
||||||
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
|
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
|
||||||
z: intersection.intersection.z
|
z: intersection.intersection.z
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.tooClose = isTooCloseToTeleport(position);
|
||||||
|
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||||
|
|
||||||
Overlays.editOverlay(this.targetOverlay, {
|
Overlays.editOverlay(this.targetOverlay, {
|
||||||
position: position,
|
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() {
|
this.disableGrab = function() {
|
||||||
Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand);
|
Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand);
|
||||||
};
|
};
|
||||||
|
@ -383,10 +458,17 @@ function Teleporter() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.teleport = function(value) {
|
this.teleport = function(value) {
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
this.exitTeleportMode();
|
this.exitTeleportMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.intersection !== null) {
|
if (this.intersection !== null) {
|
||||||
|
if (this.tooClose === true) {
|
||||||
|
this.exitTeleportMode();
|
||||||
|
this.deleteCancelOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
var offset = getAvatarFootOffset();
|
var offset = getAvatarFootOffset();
|
||||||
this.intersection.intersection.y += offset;
|
this.intersection.intersection.y += offset;
|
||||||
this.exitTeleportMode();
|
this.exitTeleportMode();
|
||||||
|
@ -394,7 +476,6 @@ function Teleporter() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.findMidpoint = function(start, end) {
|
this.findMidpoint = function(start, end) {
|
||||||
var xy = Vec3.sum(start, end);
|
var xy = Vec3.sum(start, end);
|
||||||
var midpoint = Vec3.multiply(0.5, xy);
|
var midpoint = Vec3.multiply(0.5, xy);
|
||||||
|
@ -433,13 +514,13 @@ function Teleporter() {
|
||||||
|
|
||||||
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
|
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
|
||||||
_this.deleteTargetOverlay();
|
_this.deleteTargetOverlay();
|
||||||
|
_this.deleteCancelOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
}, SMOOTH_ARRIVAL_SPACING);
|
}, SMOOTH_ARRIVAL_SPACING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//related to repositioning the avatar after you teleport
|
//related to repositioning the avatar after you teleport
|
||||||
function getAvatarFootOffset() {
|
function getAvatarFootOffset() {
|
||||||
var data = getJointData();
|
var data = getJointData();
|
||||||
|
@ -505,7 +586,11 @@ function isMoving() {
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
function isTooCloseToTeleport(position) {
|
||||||
|
return Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE;
|
||||||
|
};
|
||||||
|
|
||||||
function registerMappings() {
|
function registerMappings() {
|
||||||
mappingName = 'Hifi-Teleporter-Dev-' + Math.random();
|
mappingName = 'Hifi-Teleporter-Dev-' + Math.random();
|
||||||
|
@ -559,7 +644,7 @@ function registerMappings() {
|
||||||
}, TELEPORT_DELAY)
|
}, TELEPORT_DELAY)
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
registerMappings();
|
registerMappings();
|
||||||
|
|
||||||
|
@ -573,6 +658,7 @@ function cleanup() {
|
||||||
teleportMapping.disable();
|
teleportMapping.disable();
|
||||||
teleporter.disableMappings();
|
teleporter.disableMappings();
|
||||||
teleporter.deleteTargetOverlay();
|
teleporter.deleteTargetOverlay();
|
||||||
|
teleporter.deleteCancelOverlay();
|
||||||
teleporter.turnOffOverlayBeams();
|
teleporter.turnOffOverlayBeams();
|
||||||
if (teleporter.updateConnected !== null) {
|
if (teleporter.updateConnected !== null) {
|
||||||
Script.update.disconnect(teleporter.update);
|
Script.update.disconnect(teleporter.update);
|
||||||
|
|
Loading…
Reference in a new issue