mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-26 16:35:08 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into hdr
This commit is contained in:
commit
b133556c14
20 changed files with 274 additions and 98 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,14 +502,13 @@ 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;
|
||||||
|
|
||||||
|
if (_automaticNetworkingSetting != DISABLED_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
const QUuid& domainID = getID();
|
const QUuid& domainID = getID();
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
|
@ -499,6 +523,9 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
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
|
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||||
this, &DomainServer::performIPAddressUpdate);
|
this, &DomainServer::performIPAddressUpdate);
|
||||||
|
@ -513,6 +540,8 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::setupHeartbeatToMetaverse() {
|
void DomainServer::setupHeartbeatToMetaverse() {
|
||||||
|
@ -1139,6 +1168,8 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// check if we need to force a new temporary domain name
|
||||||
switch (requestReply.error()) {
|
switch (requestReply.error()) {
|
||||||
// if we have a temporary domain with a bad token, we get a 401
|
// if we have a temporary domain with a bad token, we get a 401
|
||||||
|
@ -1176,6 +1207,7 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
||||||
} 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.";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,7 +822,7 @@ 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();
|
||||||
|
|
||||||
|
@ -830,14 +830,30 @@ void Model::setURL(const QUrl& url) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,8 @@ public:
|
||||||
|
|
||||||
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,14 @@ public:
|
||||||
// returns 'true' if needs fullUpdate after geometry change
|
// returns 'true' if needs fullUpdate after geometry change
|
||||||
bool updateGeometry();
|
bool updateGeometry();
|
||||||
|
|
||||||
|
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 +403,8 @@ protected:
|
||||||
|
|
||||||
uint32_t _deleteGeometryCounter { 0 };
|
uint32_t _deleteGeometryCounter { 0 };
|
||||||
|
|
||||||
bool _geometryRequestFailed { false };
|
bool _visualGeometryRequestFailed { false };
|
||||||
|
bool _collisionGeometryRequestFailed { false };
|
||||||
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();
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue