diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 8f752e70d0..eabb4955d9 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -100,6 +100,63 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
+const int IEEE754_MANT_BITS = 23;
+const int IEEE754_EXPN_BIAS = 127;
+
+//
+// for x > 0.0f, returns log2(x)
+// for x <= 0.0f, returns large negative value
+//
+// abs |error| < 8e-3, smooth (exact for x=2^N) for NPOLY=3
+// abs |error| < 2e-4, smooth (exact for x=2^N) for NPOLY=5
+// rel |error| < 0.4 from precision loss very close to 1.0f
+//
+static inline float fastlog2(float x) {
+
+ union { float f; int32_t i; } mant, bits = { x };
+
+ // split into mantissa and exponent
+ mant.i = (bits.i & ((1 << IEEE754_MANT_BITS) - 1)) | (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS);
+ int32_t expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS;
+
+ mant.f -= 1.0f;
+
+ // polynomial for log2(1+x) over x=[0,1]
+ //x = (-0.346555386f * mant.f + 1.346555386f) * mant.f;
+ x = (((-0.0821307180f * mant.f + 0.321188984f) * mant.f - 0.677784014f) * mant.f + 1.43872575f) * mant.f;
+
+ return x + expn;
+}
+
+//
+// for -126 <= x < 128, returns exp2(x)
+//
+// rel |error| < 3e-3, smooth (exact for x=N) for NPOLY=3
+// rel |error| < 9e-6, smooth (exact for x=N) for NPOLY=5
+//
+static inline float fastexp2(float x) {
+
+ union { float f; int32_t i; } xi;
+
+ // bias such that x > 0
+ x += IEEE754_EXPN_BIAS;
+ //x = MAX(x, 1.0f);
+ //x = MIN(x, 254.9999f);
+
+ // split into integer and fraction
+ xi.i = (int32_t)x;
+ x -= xi.i;
+
+ // construct exp2(xi) as a float
+ xi.i <<= IEEE754_MANT_BITS;
+
+ // polynomial for exp2(x) over x=[0,1]
+ //x = (0.339766028f * x + 0.660233972f) * x + 1.0f;
+ x = (((0.0135557472f * x + 0.0520323690f) * x + 0.241379763f) * x + 0.693032121f) * x + 1.0f;
+
+ return x * xi.f;
+}
+
float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
const AvatarAudioStream& listeningNodeStream, const glm::vec3& relativePosition, bool isEcho) {
float gain = 1.0f;
@@ -148,7 +205,7 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
g = (g > 1.0f) ? 1.0f : g;
// calculate the distance coefficient using the distance to this node
- float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
+ float distanceCoefficient = fastexp2(fastlog2(g) * fastlog2(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
// multiply the current attenuation coefficient by the distance coefficient
gain *= distanceCoefficient;
diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml
index 4c937d6139..802038d806 100644
--- a/domain-server/resources/web/settings/index.shtml
+++ b/domain-server/resources/web/settings/index.shtml
@@ -25,7 +25,7 @@
-
+
diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp
index 8f8c8e001c..c827e79223 100644
--- a/domain-server/src/DomainGatekeeper.cpp
+++ b/domain-server/src/DomainGatekeeper.cpp
@@ -182,7 +182,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
#ifdef WANT_DEBUG
- qDebug() << "| user-permissions: user is in group:" << groupID << " rank:"
+ qDebug() << "| user-permissions: user " << verifiedUsername << "is in group:" << groupID << " rank:"
<< rank.name << "so:" << userPerms;
#endif
}
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 23e37efaf1..6c4b12d4c0 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -117,9 +117,18 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_settingsManager.apiRefreshGroupInformation();
setupNodeListAndAssignments();
+
+ if (_type == MetaverseDomain) {
+ // if we have a metaverse domain, we'll need an access token to heartbeat handle auto-networking
+ resetAccountManagerAccessToken();
+ }
+
setupAutomaticNetworking();
- if (!getID().isNull()) {
+
+ if (!getID().isNull() && _type != NonMetaverse) {
+ // setup periodic heartbeats to metaverse API
setupHeartbeatToMetaverse();
+
// send the first heartbeat immediately
sendHeartbeatToMetaverse();
}
@@ -301,16 +310,22 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
// store the new ID and auto networking setting on disk
_settingsManager.persistToFile();
- // change our domain ID immediately
- DependencyManager::get()->setSessionUUID(QUuid { id });
-
// store the new token to the account info
auto accountManager = DependencyManager::get();
accountManager->setTemporaryDomain(id, key);
+ // change our domain ID immediately
+ DependencyManager::get()->setSessionUUID(QUuid { id });
+
+ // change our type to reflect that we are a temporary domain now
+ _type = MetaverseTemporaryDomain;
+
// update our heartbeats to use the correct id
setupICEHeartbeatForFullNetworking();
setupHeartbeatToMetaverse();
+
+ // if we have a current ICE server address, update it in the API for the new temporary domain
+ sendICEServerAddressToMetaverseAPI();
} else {
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
<< "via domain-server relaunch or from the domain-server settings.";
@@ -394,6 +409,16 @@ void DomainServer::setupNodeListAndAssignments() {
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
if (idValueVariant) {
nodeList->setSessionUUID(idValueVariant->toString());
+
+ // if we have an ID, we'll assume we're a metaverse domain
+ // now see if we think we're a temp domain (we have an API key) or a full domain
+ const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID());
+ if (temporaryDomainKey.isEmpty()) {
+ _type = MetaverseDomain;
+ } else {
+ _type = MetaverseTemporaryDomain;
+ }
+
} else {
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
}
@@ -477,42 +502,46 @@ bool DomainServer::resetAccountManagerAccessToken() {
}
void DomainServer::setupAutomaticNetworking() {
- qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
-
- resetAccountManagerAccessToken();
_automaticNetworkingSetting =
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
- auto nodeList = DependencyManager::get();
- const QUuid& domainID = getID();
+ qDebug() << "Configuring automatic networking in domain-server as" << _automaticNetworkingSetting;
- if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
- setupICEHeartbeatForFullNetworking();
- }
+ if (_automaticNetworkingSetting != DISABLED_AUTOMATIC_NETWORKING_VALUE) {
+ const QUuid& domainID = getID();
- if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
- _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
+ if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
+ setupICEHeartbeatForFullNetworking();
+ }
- if (!domainID.isNull()) {
- qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
- << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
+ if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
+ _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
- if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
- // send any public socket changes to the data server so nodes can find us at our new IP
- connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
- this, &DomainServer::performIPAddressUpdate);
+ if (!domainID.isNull()) {
+ qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
+ << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
- // have the LNL enable public socket updating via STUN
- nodeList->startSTUNPublicSocketUpdate();
+ if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
+
+ auto nodeList = DependencyManager::get();
+
+ // send any public socket changes to the data server so nodes can find us at our new IP
+ connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
+ this, &DomainServer::performIPAddressUpdate);
+
+ // have the LNL enable public socket updating via STUN
+ nodeList->startSTUNPublicSocketUpdate();
+ }
+ } else {
+ qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
+ << "Please add an ID to your config file or via the web interface.";
+
+ return;
}
- } else {
- qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
- << "Please add an ID to your config file or via the web interface.";
-
- return;
}
}
+
}
void DomainServer::setupHeartbeatToMetaverse() {
@@ -1139,42 +1168,45 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
return;
}
- // check if we need to force a new temporary domain name
- switch (requestReply.error()) {
- // if we have a temporary domain with a bad token, we get a 401
- case QNetworkReply::NetworkError::AuthenticationRequiredError: {
- static const QString DATA_KEY = "data";
- static const QString TOKEN_KEY = "api_key";
+ // only attempt to grab a new temporary name if we're already a temporary domain server
+ if (_type == MetaverseTemporaryDomain) {
+ // check if we need to force a new temporary domain name
+ switch (requestReply.error()) {
+ // if we have a temporary domain with a bad token, we get a 401
+ case QNetworkReply::NetworkError::AuthenticationRequiredError: {
+ static const QString DATA_KEY = "data";
+ static const QString TOKEN_KEY = "api_key";
- QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
- auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
+ QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
+ auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
- if (!tokenFailure.isNull()) {
- qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
+ if (!tokenFailure.isNull()) {
+ qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
+ }
+ break;
}
- break;
+ // if the domain does not (or no longer) exists, we get a 404
+ case QNetworkReply::NetworkError::ContentNotFoundError:
+ qWarning() << "Domain not found, getting a new temporary domain.";
+ break;
+ // otherwise, we erred on something else, and should not force a temporary domain
+ default:
+ return;
}
- // if the domain does not (or no longer) exists, we get a 404
- case QNetworkReply::NetworkError::ContentNotFoundError:
- qWarning() << "Domain not found, getting a new temporary domain.";
- break;
- // otherwise, we erred on something else, and should not force a temporary domain
- default:
- return;
- }
- // halt heartbeats until we have a token
- _metaverseHeartbeatTimer->deleteLater();
- _metaverseHeartbeatTimer = nullptr;
+ // halt heartbeats until we have a token
+ _metaverseHeartbeatTimer->deleteLater();
+ _metaverseHeartbeatTimer = nullptr;
- // give up eventually to avoid flooding traffic
- static const int MAX_ATTEMPTS = 5;
- static int attempt = 0;
- if (++attempt < MAX_ATTEMPTS) {
- // get a new temporary name and token
- getTemporaryName(true);
- } else {
- qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
+ // give up eventually to avoid flooding traffic
+ static const int MAX_ATTEMPTS = 5;
+ static int attempt = 0;
+ if (++attempt < MAX_ATTEMPTS) {
+ // get a new temporary name and token
+ getTemporaryName(true);
+ } else {
+ qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
+ }
}
}
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index 4004333789..06b3ed2c0a 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -42,6 +42,12 @@ public:
DomainServer(int argc, char* argv[]);
~DomainServer();
+ enum DomainType {
+ NonMetaverse,
+ MetaverseDomain,
+ MetaverseTemporaryDomain
+ };
+
static int const EXIT_CODE_REBOOT;
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
@@ -195,6 +201,8 @@ private:
int _numHeartbeatDenials { 0 };
bool _connectedToICEServer { false };
+ DomainType _type { DomainType::NonMetaverse };
+
friend class DomainGatekeeper;
friend class DomainMetadata;
};
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index dc49bc6126..47187fac5c 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -356,7 +356,7 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap&
if (nameKey.first.toLower() != groupNameLower) {
continue;
}
- QUuid groupID = _groupIDs[groupNameLower];
+ QUuid groupID = _groupIDs[groupNameLower.toLower()];
QUuid rankID = nameKey.second;
GroupRank rank = _groupRanks[groupID][rankID];
if (rank.order == 0) {
@@ -1477,14 +1477,14 @@ void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& r
void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) {
if (rankID != QUuid()) {
- _groupMembership[name][groupID] = rankID;
+ _groupMembership[name.toLower()][groupID] = rankID;
} else {
- _groupMembership[name].remove(groupID);
+ _groupMembership[name.toLower()].remove(groupID);
}
}
QUuid DomainServerSettingsManager::isGroupMember(const QString& name, const QUuid& groupID) {
- const QHash& groupsForName = _groupMembership[name];
+ const QHash& groupsForName = _groupMembership[name.toLower()];
if (groupsForName.contains(groupID)) {
return groupsForName[groupID];
}
@@ -1528,7 +1528,7 @@ void DomainServerSettingsManager::debugDumpGroupsState() {
qDebug() << "_groupIDs:";
foreach (QString groupName, _groupIDs.keys()) {
- qDebug() << "| " << groupName << "==>" << _groupIDs[groupName];
+ qDebug() << "| " << groupName << "==>" << _groupIDs[groupName.toLower()];
}
qDebug() << "_groupNames:";
@@ -1548,7 +1548,7 @@ void DomainServerSettingsManager::debugDumpGroupsState() {
qDebug() << "_groupMembership";
foreach (QString userName, _groupMembership.keys()) {
- QHash& groupsForUser = _groupMembership[userName];
+ QHash& groupsForUser = _groupMembership[userName.toLower()];
QString line = "";
foreach (QUuid groupID, groupsForUser.keys()) {
line += " g=" + groupID.toString() + ",r=" + groupsForUser[groupID].toString();
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 144589326c..c067377ffc 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -84,7 +84,7 @@ public:
QList getBlacklistGroupIDs();
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
- void clearGroupMemberships(const QString& name) { _groupMembership[name].clear(); }
+ void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); }
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member
diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml
index 8d971e48d3..1ad2d1a1e4 100644
--- a/interface/resources/qml/AssetServer.qml
+++ b/interface/resources/qml/AssetServer.qml
@@ -314,6 +314,14 @@ ScrollingWindow {
});
}
+ Timer {
+ id: doUploadTimer
+ property var url
+ property bool isConnected: false
+ interval: 5
+ repeat: false
+ running: false
+ }
property var uploadOpen: false;
Timer {
@@ -366,6 +374,10 @@ ScrollingWindow {
}, dropping);
}
+ function initiateUpload(url) {
+ doUpload(doUploadTimer.url, false);
+ }
+
if (fileUrl) {
doUpload(fileUrl, true);
} else {
@@ -373,12 +385,21 @@ ScrollingWindow {
selectDirectory: false,
dir: currentDirectory
});
+
browser.canceled.connect(function() {
uploadOpen = false;
});
+
browser.selectedFile.connect(function(url) {
currentDirectory = browser.dir;
- doUpload(url, false);
+
+ // Initiate upload from a timer so that file browser dialog can close beforehand.
+ doUploadTimer.url = url;
+ if (!doUploadTimer.isConnected) {
+ doUploadTimer.triggered.connect(function() { initiateUpload(); });
+ doUploadTimer.isConnected = true;
+ }
+ doUploadTimer.start();
});
}
}
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 4d9481f002..b1b30a1acd 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -98,6 +98,7 @@ Avatar::Avatar(RigPointer rig) :
_headData = static_cast(new Head(this));
_skeletonModel = std::make_shared(this, nullptr, rig);
+ connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
}
Avatar::~Avatar() {
@@ -298,7 +299,9 @@ void Avatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("head");
glm::vec3 headPosition = getPosition();
- _skeletonModel->getHeadPosition(headPosition);
+ if (!_skeletonModel->getHeadPosition(headPosition)) {
+ headPosition = getPosition();
+ }
Head* head = getHead();
head->setPosition(headPosition);
head->setScale(getUniformScale());
@@ -306,6 +309,7 @@ void Avatar::simulate(float deltaTime) {
}
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
+ getHead()->setPosition(getPosition());
_skeletonModel->simulate(deltaTime, false);
}
@@ -916,6 +920,17 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
}
}
+void Avatar::setModelURLFinished(bool success) {
+ if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
+ qDebug() << "Using default after failing to load Avatar model: " << _skeletonModelURL;
+ // call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
+ // we don't redo this every time we receive an identity packet from the avatar with the bad url.
+ QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
+ Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
+ }
+}
+
+
// create new model, can return an instance of a SoftAttachmentModel rather then Model
static std::shared_ptr allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
if (isSoft) {
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index b9f44613c7..910f2cc1e6 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -184,6 +184,8 @@ public slots:
glm::vec3 getRightPalmPosition() const;
glm::quat getRightPalmRotation() const;
+ void setModelURLFinished(bool success);
+
protected:
friend class AvatarManager;
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index eef176338f..de5455fc14 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -430,6 +430,7 @@ void MyAvatar::simulate(float deltaTime) {
if (!_skeletonModel->hasSkeleton()) {
// All the simulation that can be done has been done
+ getHead()->setPosition(getPosition()); // so audio-position isn't 0,0,0
return;
}
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 564a58708f..eba2d4cf4b 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -919,7 +919,9 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
bool RenderableModelEntityItem::shouldBePhysical() const {
// If we have a model, make sure it hasn't failed to download.
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
- if (_model && _model->didGeometryRequestFail()) {
+ if (_model && getShapeType() == SHAPE_TYPE_COMPOUND && _model->didCollisionGeometryRequestFail()) {
+ return false;
+ } else if (_model && getShapeType() != SHAPE_TYPE_NONE && _model->didVisualGeometryRequestFail()) {
return false;
} else {
return ModelEntityItem::shouldBePhysical();
diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp
index c4b2a6dd22..3b7092ce8d 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.cpp
+++ b/libraries/model-networking/src/model-networking/ModelCache.cpp
@@ -403,9 +403,8 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) {
void GeometryResourceWatcher::resourceFinished(bool success) {
if (success) {
_geometryRef = std::make_shared(*_resource);
- } else {
- emit resourceFailed();
}
+ emit finished(success);
}
void GeometryResourceWatcher::resourceRefreshed() {
diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h
index 62037d67bc..962a919d6c 100644
--- a/libraries/model-networking/src/model-networking/ModelCache.h
+++ b/libraries/model-networking/src/model-networking/ModelCache.h
@@ -112,7 +112,7 @@ public:
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
signals:
- void resourceFailed();
+ void finished(bool success);
private:
void startWatching();
diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp
index 30fb3536ca..b5ef03be0f 100644
--- a/libraries/physics/src/CharacterController.cpp
+++ b/libraries/physics/src/CharacterController.cpp
@@ -534,13 +534,14 @@ void CharacterController::preSimulation() {
// scan for distant floor
// rayStart is at center of bottom sphere
- btVector3 rayStart = _characterBodyTransform.getOrigin() - _halfHeight * _currentUp;
+ btVector3 rayStart = _characterBodyTransform.getOrigin();
// rayEnd is straight down MAX_FALL_HEIGHT
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
btVector3 rayEnd = rayStart - rayLength * _currentUp;
- const btScalar JUMP_PROXIMITY_THRESHOLD = 0.1f * _radius;
+ const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
+ const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
const btScalar MIN_HOVER_HEIGHT = 2.5f;
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
@@ -553,7 +554,7 @@ void CharacterController::preSimulation() {
bool rayHasHit = rayCallback.hasHit();
if (rayHasHit) {
_rayHitStartTime = now;
- _floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
+ _floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
rayHasHit = true;
} else {
@@ -581,7 +582,7 @@ void CharacterController::preSimulation() {
_takeoffJumpButtonID = _jumpButtonDownCount;
_takeoffToInAirStartTime = now;
SET_STATE(State::Takeoff, "jump pressed");
- } else if (rayHasHit && !_hasSupport && _floorDistance > JUMP_PROXIMITY_THRESHOLD) {
+ } else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) {
SET_STATE(State::InAir, "falling");
}
break;
@@ -595,7 +596,7 @@ void CharacterController::preSimulation() {
}
break;
case State::InAir: {
- if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport)) {
+ if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
SET_STATE(State::Ground, "hit ground");
} else {
btVector3 desiredVelocity = _targetVelocity;
@@ -614,7 +615,7 @@ void CharacterController::preSimulation() {
case State::Hover:
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
SET_STATE(State::InAir, "near ground");
- } else if (((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport) && !flyingFast) {
+ } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
SET_STATE(State::Ground, "touching ground");
}
break;
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 581bd285e2..d755dc3aca 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -111,8 +111,8 @@ Model::Model(RigPointer rig, QObject* parent) :
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
- // handle download failure reported by the GeometryResourceWatcher
- connect(&_renderWatcher, &GeometryResourceWatcher::resourceFailed, this, &Model::handleGeometryResourceFailure);
+ connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);
+ connect(&_collisionWatcher, &GeometryResourceWatcher::finished, this, &Model::loadCollisionModelURLFinished);
}
Model::~Model() {
@@ -822,7 +822,7 @@ void Model::setURL(const QUrl& url) {
_needsReload = true;
_needsUpdateTextures = true;
_meshGroupsKnown = false;
- _geometryRequestFailed = false;
+ _visualGeometryRequestFailed = false;
invalidCalculatedMeshBoxes();
deleteGeometry();
@@ -830,14 +830,30 @@ void Model::setURL(const QUrl& url) {
onInvalidate();
}
+void Model::loadURLFinished(bool success) {
+ if (!success) {
+ _visualGeometryRequestFailed = true;
+ }
+ emit setURLFinished(success);
+}
+
void Model::setCollisionModelURL(const QUrl& url) {
if (_collisionUrl == url && _collisionWatcher.getURL() == url) {
return;
}
_collisionUrl = url;
+ _collisionGeometryRequestFailed = false;
_collisionWatcher.setResource(DependencyManager::get()->getGeometryResource(url));
}
+void Model::loadCollisionModelURLFinished(bool success) {
+ if (!success) {
+ _collisionGeometryRequestFailed = true;
+ }
+
+ emit setCollisionModelURLFinished(success);
+}
+
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
}
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index b95c0318b4..f7bf83ca5b 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -148,8 +148,9 @@ public:
const QUrl& getCollisionURL() const { return _collisionUrl; }
bool isActive() const { return isLoaded(); }
-
- bool didGeometryRequestFail() const { return _geometryRequestFailed; }
+
+ bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; }
+ bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; }
bool convexHullContains(glm::vec3 point);
@@ -237,6 +238,14 @@ public:
// returns 'true' if needs fullUpdate after geometry change
bool updateGeometry();
+public slots:
+ void loadURLFinished(bool success);
+ void loadCollisionModelURLFinished(bool success);
+
+signals:
+ void setURLFinished(bool success);
+ void setCollisionModelURLFinished(bool success);
+
protected:
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
@@ -394,10 +403,8 @@ protected:
uint32_t _deleteGeometryCounter { 0 };
- bool _geometryRequestFailed { false };
-
-private slots:
- void handleGeometryResourceFailure() { _geometryRequestFailed = true; }
+ bool _visualGeometryRequestFailed { false };
+ bool _collisionGeometryRequestFailed { false };
};
Q_DECLARE_METATYPE(ModelPointer)
diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp
index 2114289095..40234e8134 100644
--- a/libraries/script-engine/src/ScriptCache.cpp
+++ b/libraries/script-engine/src/ScriptCache.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -109,12 +110,22 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
QUrl unnormalizedURL(scriptOrURL);
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
- // attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the entityScript use case)
- if (url.scheme().isEmpty() && scriptOrURL.simplified().replace(" ", "").contains("(function(){")) {
+ // attempt to determine if this is a URL to a script, or if this is actually a script itself (which is valid in the
+ // entityScript use case)
+ if (unnormalizedURL.scheme().isEmpty() &&
+ scriptOrURL.simplified().replace(" ", "").contains(QRegularExpression(R"(\(function\([a-z]?[\w,]*\){)"))) {
contentAvailable(scriptOrURL, scriptOrURL, false, true);
return;
}
+ // give a similar treatment to javacript: urls
+ if (unnormalizedURL.scheme() == "javascript") {
+ QString contents { scriptOrURL };
+ contents.replace(QRegularExpression("^javascript:"), "");
+ contentAvailable(scriptOrURL, contents, false, true);
+ return;
+ }
+
Lock lock(_containerLock);
if (_scriptCache.contains(url) && !forceDownload) {
auto scriptContent = _scriptCache[url];
@@ -133,6 +144,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
auto request = ResourceManager::createResourceRequest(nullptr, url);
+ Q_ASSERT(request);
request->setCacheEnabled(!forceDownload);
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
request->send();
diff --git a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
index 30567b4fc7..daef1d5db3 100644
--- a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
+++ b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
@@ -40,7 +40,7 @@ var DEFAULT_SOUND_DATA = {
Script.include("../../libraries/utils.js");
Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList.
-Avatar.skeletonModelURL = "http://invalid-url";
+Avatar.skeletonModelURL = "http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst";
function ignore() {}
function debug() { // Display the arguments not just [Object object].
//print.apply(null, [].map.call(arguments, JSON.stringify));
diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index 9e11f839b3..f29fdfb00a 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -107,7 +107,10 @@ var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for ne
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
-var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds
+
+// if an equipped item is "adjusted" to be too far from the hand it's in, it will be unequipped.
+var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks
+var AUTO_UNEQUIP_DISTANCE_FACTOR = 1.2; // multiplied by maximum dimension of held item, > this means drop
//
// other constants
@@ -1818,7 +1821,8 @@ function MyController(hand) {
this.heartBeat(this.grabbedEntity);
- var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]);
+ var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID",
+ "position", "rotation", "dimensions"]);
if (!props.position) {
// server may have reset, taking our equipped entity with it. move back to "off" stte
this.callEntityMethodOnGrabbed("releaseGrab");
@@ -1830,10 +1834,9 @@ function MyController(hand) {
if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) {
this.lastUnequipCheckTime = now;
- if (props.parentID == MyAvatar.sessionUUID &&
- Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) {
+ if (props.parentID == MyAvatar.sessionUUID) {
var handPosition = this.getHandPosition();
- // the center of the equipped object being far from the hand isn't enough to autoequip -- we also
+ // the center of the equipped object being far from the hand isn't enough to auto-unequip -- we also
// need to fail the findEntities test.
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
@@ -2416,4 +2419,4 @@ function cleanup() {
}
Script.scriptEnding.connect(cleanup);
-Script.update.connect(update);
\ No newline at end of file
+Script.update.connect(update);