diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index d763d1abe7..48ffc2fdbc 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1063,6 +1063,12 @@ void OctreeServer::readConfiguration() { _wantBackup = !noBackup; qDebug() << "wantBackup=" << _wantBackup; + if (!readOptionString("backupDirectoryPath", settingsSectionObject, _backupDirectoryPath)) { + _backupDirectoryPath = ""; + } + + qDebug() << "backupDirectoryPath=" << _backupDirectoryPath; + readOptionBool(QString("persistFileDownload"), settingsSectionObject, _persistFileDownload); qDebug() << "persistFileDownload=" << _persistFileDownload; @@ -1160,25 +1166,25 @@ void OctreeServer::domainSettingsRequestComplete() { // If persist filename does not exist, let's see if there is one beside the application binary // If there is, let's copy it over to our target persist directory QDir persistPath { _persistFilePath }; - QString absoluteFilePath = persistPath.absolutePath(); + QString persistAbsoluteFilePath = persistPath.absolutePath(); if (persistPath.isRelative()) { // if the domain settings passed us a relative path, make an absolute path that is relative to the // default data directory - absoluteFilePath = QDir(ServerPathUtils::getDataFilePath("entities/")).absoluteFilePath(_persistFilePath); + persistAbsoluteFilePath = QDir(ServerPathUtils::getDataFilePath("entities/")).absoluteFilePath(_persistFilePath); } static const QString ENTITY_PERSIST_EXTENSION = ".json.gz"; // force the persist file to end with .json.gz - if (!absoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) { - absoluteFilePath += ENTITY_PERSIST_EXTENSION; + if (!persistAbsoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) { + persistAbsoluteFilePath += ENTITY_PERSIST_EXTENSION; } else { // make sure the casing of .json.gz is correct - absoluteFilePath.replace(ENTITY_PERSIST_EXTENSION, ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive); + persistAbsoluteFilePath.replace(ENTITY_PERSIST_EXTENSION, ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive); } - if (!QFile::exists(absoluteFilePath)) { + if (!QFile::exists(persistAbsoluteFilePath)) { qDebug() << "Persist file does not exist, checking for existence of persist file next to application"; static const QString OLD_DEFAULT_PERSIST_FILENAME = "resources/models.json.gz"; @@ -1204,7 +1210,7 @@ void OctreeServer::domainSettingsRequestComplete() { pathToCopyFrom = oldDefaultPersistPath; } - QDir persistFileDirectory { QDir::cleanPath(absoluteFilePath + "/..") }; + QDir persistFileDirectory { QDir::cleanPath(persistAbsoluteFilePath + "/..") }; if (!persistFileDirectory.exists()) { qDebug() << "Creating data directory " << persistFileDirectory.absolutePath(); @@ -1212,16 +1218,46 @@ void OctreeServer::domainSettingsRequestComplete() { } if (shouldCopy) { - qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << absoluteFilePath; + qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << persistAbsoluteFilePath; - QFile::copy(pathToCopyFrom, absoluteFilePath); + QFile::copy(pathToCopyFrom, persistAbsoluteFilePath); } else { qDebug() << "No existing persist file found"; } } + + auto persistFileDirectory = QFileInfo(persistAbsoluteFilePath).absolutePath(); + if (_backupDirectoryPath.isEmpty()) { + // Use the persist file's directory to store backups + _backupDirectoryPath = persistFileDirectory; + } else { + // The backup directory has been set. + // If relative, make it relative to the entities directory in the application data directory + // If absolute, no resolution is necessary + QDir backupDirectory { _backupDirectoryPath }; + QString absoluteBackupDirectory; + if (backupDirectory.isRelative()) { + absoluteBackupDirectory = QDir(ServerPathUtils::getDataFilePath("entities/")).absoluteFilePath(_backupDirectoryPath); + absoluteBackupDirectory = QDir(absoluteBackupDirectory).absolutePath(); + } else { + absoluteBackupDirectory = backupDirectory.absolutePath(); + } + backupDirectory = QDir(absoluteBackupDirectory); + if (!backupDirectory.exists()) { + if (backupDirectory.mkpath(".")) { + qDebug() << "Created backup directory"; + } else { + qDebug() << "ERROR creating backup directory, using persist file directory"; + _backupDirectoryPath = persistFileDirectory; + } + } else { + _backupDirectoryPath = absoluteBackupDirectory; + } + } + qDebug() << "Backups will be stored in: " << _backupDirectoryPath; // now set up PersistThread - _persistThread = new OctreePersistThread(_tree, absoluteFilePath, _persistInterval, + _persistThread = new OctreePersistThread(_tree, persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); _persistThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index beea322e9b..2bcf36628d 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -172,6 +172,7 @@ protected: QString _persistFilePath; QString _persistAsFileType; + QString _backupDirectoryPath; int _packetsPerClientPerInterval; int _packetsTotalPerInterval; OctreePointer _tree; // this IS a reaveraging tree diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c888fa301b..c9d7ea77d5 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1108,6 +1108,14 @@ "default": "models.json.gz", "advanced": true }, + { + "name": "backupDirectoryPath", + "label": "Entities Backup Directory Path", + "help": "The path to the directory to store backups in.
If this path is relative it will be relative to the application data directory.", + "placeholder": "", + "default": "", + "advanced": true + }, { "name": "persistInterval", "label": "Save Check Interval", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index c827e79223..23a53c3eb0 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -509,9 +509,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, } } else { if (!senderSockAddr.isNull()) { - qDebug() << "Insufficient data to decrypt username signature - denying connection."; - sendConnectionDeniedPacket("Insufficient data", senderSockAddr, - DomainHandler::ConnectionRefusedReason::LoginError); + qDebug() << "Insufficient data to decrypt username signature - delaying connection."; } } diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 222357ac9d..c9e91c8666 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -11,7 +11,7 @@ [ { "type": "deadZone", "min": 0.15 }, "constrainToInteger", - { "type": "pulse", "interval": 0.5 }, + { "type": "pulse", "interval": 0.25 }, { "type": "scale", "scale": 22.5 } ] }, diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 79114b8141..dce3e9660c 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -4,8 +4,8 @@ { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, { - "from": "Vive.LT", "to": "Standard.LT", - "filters": [ + "from": "Vive.LT", "to": "Standard.LT", + "filters": [ { "type": "deadZone", "min": 0.05 } ] }, @@ -18,8 +18,8 @@ { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, { - "from": "Vive.RT", "to": "Standard.RT", - "filters": [ + "from": "Vive.RT", "to": "Standard.RT", + "filters": [ { "type": "deadZone", "min": 0.05 } ] }, @@ -37,4 +37,4 @@ { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, { "from": "Vive.RightHand", "to": "Standard.RightHand" } ] -} +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c17b9963e5..836b48b3fb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1138,10 +1138,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : static int SEND_STATS_INTERVAL_MS = 10000; static int NEARBY_AVATAR_RADIUS_METERS = 10; + static glm::vec3 lastAvatarPosition = getMyAvatar()->getPosition(); + static glm::mat4 lastHMDHeadPose = getHMDSensorPose(); + static controller::Pose lastLeftHandPose = getMyAvatar()->getLeftHandPose(); + static controller::Pose lastRightHandPose = getMyAvatar()->getRightHandPose(); + // Periodically send fps as a user activity event QTimer* sendStatsTimer = new QTimer(this); sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); connect(sendStatsTimer, &QTimer::timeout, this, [this]() { + QJsonObject properties = {}; MemoryInfo memInfo; if (getMemoryInfo(memInfo)) { @@ -1183,6 +1189,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; + glm::vec3 avatarPosition = getMyAvatar()->getPosition(); + properties["avatar_has_moved"] = lastAvatarPosition != avatarPosition; + lastAvatarPosition = avatarPosition; + + auto entityScriptingInterface = DependencyManager::get(); + auto entityActivityTracking = entityScriptingInterface->getActivityTracking(); + entityScriptingInterface->resetActivityTracking(); + properties["added_entity_cnt"] = entityActivityTracking.addedEntityCount; + properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount; + properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount; + + auto hmdHeadPose = getHMDSensorPose(); + properties["hmd_head_pose_changed"] = isHMDMode() && (hmdHeadPose != lastHMDHeadPose); + lastHMDHeadPose = hmdHeadPose; + + auto leftHandPose = getMyAvatar()->getLeftHandPose(); + auto rightHandPose = getMyAvatar()->getRightHandPose(); + // controller::Pose considers two poses to be different if either are invalid. In our case, we actually + // want to consider the pose to be unchanged if it was invalid and still is invalid, so we check that first. + properties["hand_pose_changed"] = + ((leftHandPose.valid || lastLeftHandPose.valid) && (leftHandPose != lastLeftHandPose)) + || ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose)); + lastLeftHandPose = leftHandPose; + lastRightHandPose = rightHandPose; + UserActivityLogger::getInstance().logAction("stats", properties); }); sendStatsTimer->start(); @@ -1237,6 +1268,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _defaultSkybox->setCubemap(_defaultSkyboxTexture); _defaultSkybox->setColor({ 1.0, 1.0, 1.0 }); + EntityItem::setEntitiesShouldFadeFunction([this]() { + SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); + return entityServerNode && !isPhysicsEnabled(); + }); + // After all of the constructor is completed, then set firstRun to false. Setting::Handle firstRun{ Settings::firstRun, true }; firstRun.set(false); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 57e379a9ac..782ecbcc64 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -516,13 +516,23 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const { return _sensorToWorldMatrixCache.get(); } +// As far as I know no HMD system supports a play area of a kilometer in radius. +static const float MAX_HMD_ORIGIN_DISTANCE = 1000.0f; // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD // as it moves through the world. void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { // update the sensorMatrices based on the new hmd pose _hmdSensorMatrix = hmdSensorMatrix; - _hmdSensorPosition = extractTranslation(hmdSensorMatrix); + auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix); + + if (newHmdSensorPosition != _hmdSensorPosition && + glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) { + qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition; + // Ignore unreasonable HMD sensor data + return; + } + _hmdSensorPosition = newHmdSensorPosition; _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index b9f384f013..29cbfd79e6 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -35,6 +35,7 @@ int EntityItem::_maxActionsDataSize = 800; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; +std::function EntityItem::_entitiesShouldFadeFunction = [](){ return true; }; EntityItem::EntityItem(const EntityItemID& entityItemID) : SpatiallyNestable(NestableType::Entity, entityItemID), diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index fd2cf41b77..45e178a8dc 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -432,6 +432,8 @@ public: QUuid getOwningAvatarID() const { return _owningAvatarID; } void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + static void setEntitiesShouldFadeFunction(std::function func) { _entitiesShouldFadeFunction = func; } + static std::function getEntitiesShouldFadeFunction() { return _entitiesShouldFadeFunction; } virtual bool isTransparent() { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; } protected: @@ -564,7 +566,8 @@ protected: quint64 _lastUpdatedAccelerationTimestamp { 0 }; quint64 _fadeStartTime { usecTimestampNow() }; - bool _isFading { true }; + static std::function _entitiesShouldFadeFunction; + bool _isFading { _entitiesShouldFadeFunction() }; }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 232b952a93..1961742c2e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -40,6 +40,12 @@ void EntityScriptingInterface::queueEntityMessage(PacketType packetType, getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties); } +void EntityScriptingInterface::resetActivityTracking() { + _activityTracking.addedEntityCount = 0; + _activityTracking.deletedEntityCount = 0; + _activityTracking.editedEntityCount = 0; +} + bool EntityScriptingInterface::canAdjustLocks() { auto nodeList = DependencyManager::get(); return nodeList->isAllowedEditor(); @@ -162,6 +168,8 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { + _activityTracking.addedEntityCount++; + EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); @@ -232,6 +240,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& shapeType, bool dynamic, const glm::vec3& position, const glm::vec3& gravity) { + _activityTracking.addedEntityCount++; + EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setName(name); @@ -295,6 +305,8 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit } QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { + _activityTracking.editedEntityCount++; + EntityItemProperties properties = scriptSideProperties; auto dimensions = properties.getDimensions(); @@ -438,6 +450,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } void EntityScriptingInterface::deleteEntity(QUuid id) { + _activityTracking.deletedEntityCount++; + EntityItemID entityID(id); bool shouldDelete = true; diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index afc529bc53..e3c659cf6b 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -65,6 +65,13 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende public: EntityScriptingInterface(bool bidOnSimulationOwnership); + class ActivityTracking { + public: + int addedEntityCount { 0 }; + int deletedEntityCount { 0 }; + int editedEntityCount { 0 }; + }; + EntityEditPacketSender* getEntityPacketSender() const { return (EntityEditPacketSender*)getPacketSender(); } virtual NodeType_t getServerNodeType() const override { return NodeType::EntityServer; } virtual OctreeEditPacketSender* createPacketSender() override { return new EntityEditPacketSender(); } @@ -73,6 +80,9 @@ public: EntityTreePointer getEntityTree() { return _entityTree; } void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine); float calculateCost(float mass, float oldVelocity, float newVelocity); + + void resetActivityTracking(); + ActivityTracking getActivityTracking() const { return _activityTracking; } public slots: // returns true if the DomainServer will allow this Node/Avatar to make changes @@ -227,6 +237,7 @@ private: float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } void setCurrentAvatarEnergy(float energy); + ActivityTracking _activityTracking; float costMultiplier = { 0.01f }; float getCostMultiplier(); void setCostMultiplier(float value); diff --git a/libraries/gpu/src/gpu/Buffer.h b/libraries/gpu/src/gpu/Buffer.h index d2f52180c5..5bcfb2cbab 100644 --- a/libraries/gpu/src/gpu/Buffer.h +++ b/libraries/gpu/src/gpu/Buffer.h @@ -293,10 +293,18 @@ public: template Iterator end() { return Iterator(&edit(getNum()), _stride); } #else template Iterator begin() const { return Iterator(&get(), _stride); } - template Iterator end() const { return Iterator(&get(getNum()), _stride); } + template Iterator end() const { + // reimplement get without bounds checking + Resource::Size elementOffset = getNum() * _stride + _offset; + return Iterator((reinterpret_cast (_buffer->getData() + elementOffset)), _stride); + } #endif template Iterator cbegin() const { return Iterator(&get(), _stride); } - template Iterator cend() const { return Iterator(&get(getNum()), _stride); } + template Iterator cend() const { + // reimplement get without bounds checking + Resource::Size elementOffset = getNum() * _stride + _offset; + return Iterator((reinterpret_cast (_buffer->getData() + elementOffset)), _stride); + } // the number of elements of the specified type fitting in the view size template Index getNum() const { diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index d48c35d542..7034790eaf 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -34,11 +34,12 @@ const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds -OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, int persistInterval, +OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval, bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, QString persistAsFileType) : _tree(tree), _filename(filename), + _backupDirectory(backupDirectory), _persistInterval(persistInterval), _initialLoadComplete(false), _loadTimeUSecs(0), @@ -316,7 +317,7 @@ bool OctreePersistThread::getMostRecentBackup(const QString& format, // Based on our backup file name, determine the path and file name pattern for backup files QFileInfo persistFileInfo(_filename); - QString path = persistFileInfo.path(); + QString path = _backupDirectory; QString fileNamePart = persistFileInfo.fileName(); QStringList filters; @@ -369,10 +370,12 @@ void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { if (rule.maxBackupVersions > 0) { qCDebug(octree) << "Rolling old backup versions for rule" << rule.name << "..."; + QString backupFileName = _backupDirectory + "/" + QUrl(_filename).fileName(); + // Delete maximum rolling file because rename() fails on Windows if target exists QString backupMaxExtensionN = rule.extensionFormat; backupMaxExtensionN.replace(QString("%N"), QString::number(rule.maxBackupVersions)); - QString backupMaxFilenameN = _filename + backupMaxExtensionN; + QString backupMaxFilenameN = backupFileName + backupMaxExtensionN; QFile backupMaxFileN(backupMaxFilenameN); if (backupMaxFileN.exists()) { int result = remove(qPrintable(backupMaxFilenameN)); @@ -387,8 +390,8 @@ void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { backupExtensionN.replace(QString("%N"), QString::number(n)); backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1)); - QString backupFilenameN = findMostRecentFileExtension(_filename, PERSIST_EXTENSIONS) + backupExtensionN; - QString backupFilenameNplusOne = _filename + backupExtensionNplusOne; + QString backupFilenameN = findMostRecentFileExtension(backupFileName, PERSIST_EXTENSIONS) + backupExtensionN; + QString backupFilenameNplusOne = backupFileName + backupExtensionNplusOne; QFile backupFileN(backupFilenameN); @@ -434,21 +437,20 @@ void OctreePersistThread::backup() { struct tm* localTime = localtime(&_lastPersistTime); - QString backupFileName; + QString backupFileName = _backupDirectory + "/" + QUrl(_filename).fileName(); // check to see if they asked for version rolling format if (rule.extensionFormat.contains("%N")) { rollOldBackupVersions(rule); // rename all the old backup files accordingly QString backupExtension = rule.extensionFormat; backupExtension.replace(QString("%N"), QString("1")); - backupFileName = _filename + backupExtension; + backupFileName += backupExtension; } else { char backupExtension[256]; strftime(backupExtension, sizeof(backupExtension), qPrintable(rule.extensionFormat), localTime); - backupFileName = _filename + backupExtension; + backupFileName += backupExtension; } - if (rule.maxBackupVersions > 0) { QFile persistFile(_filename); if (persistFile.exists()) { diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index d0f0f03f98..f8215fb34a 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -33,9 +33,9 @@ public: static const int DEFAULT_PERSIST_INTERVAL; - OctreePersistThread(OctreePointer tree, const QString& filename, int persistInterval = DEFAULT_PERSIST_INTERVAL, - bool wantBackup = false, const QJsonObject& settings = QJsonObject(), - bool debugTimestampNow = false, QString persistAsFileType="svo"); + OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, + int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, + const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, QString persistAsFileType="svo"); bool isInitialLoadComplete() const { return _initialLoadComplete; } quint64 getLoadElapsedTime() const { return _loadTimeUSecs; } @@ -64,6 +64,7 @@ protected: private: OctreePointer _tree; QString _filename; + QString _backupDirectory; int _persistInterval; bool _initialLoadComplete; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 38d181e748..63082a8995 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -15,6 +15,7 @@ #include "DeferredLightingEffect.h" #include "Model.h" +#include "EntityItem.h" using namespace render; @@ -517,10 +518,16 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline: } void ModelMeshPartPayload::startFade() { - _fadeStartTime = usecTimestampNow(); - _hasStartedFade = true; - _prevHasStartedFade = false; - _hasFinishedFade = false; + bool shouldFade = EntityItem::getEntitiesShouldFadeFunction()(); + if (shouldFade) { + _fadeStartTime = usecTimestampNow(); + _hasStartedFade = true; + _hasFinishedFade = false; + } else { + _isFading = true; + _hasStartedFade = true; + _hasFinishedFade = true; + } } void ModelMeshPartPayload::render(RenderArgs* args) const { @@ -533,10 +540,11 @@ void ModelMeshPartPayload::render(RenderArgs* args) const { // When an individual mesh parts like this finishes its fade, we will mark the Model as // having render items that need updating bool nextIsFading = _isFading ? isStillFading() : false; - if (_isFading != nextIsFading || _prevHasStartedFade != _hasStartedFade) { - _isFading = nextIsFading || _prevHasStartedFade != _hasStartedFade; - _hasFinishedFade = _prevHasStartedFade == _hasStartedFade && !_isFading; - _prevHasStartedFade = _hasStartedFade; + bool startFading = !_isFading && !_hasFinishedFade && _hasStartedFade; + bool endFading = _isFading && !nextIsFading; + if (startFading || endFading) { + _isFading = startFading; + _hasFinishedFade = endFading; _model->setRenderItemsNeedUpdate(); } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 67fb660f8b..29478b3b4e 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -110,7 +110,6 @@ public: private: quint64 _fadeStartTime { 0 }; bool _hasStartedFade { false }; - mutable bool _prevHasStartedFade{ false }; mutable bool _hasFinishedFade { false }; mutable bool _isFading { false }; }; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index b5f360ad8d..e751427ce2 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -108,19 +108,20 @@ public: } void updateSource() { - Lock lock(_plugin._presentMutex); - while (!_queue.empty()) { - auto& front = _queue.front(); - auto result = glClientWaitSync(front.fence, 0, 0); - if (GL_TIMEOUT_EXPIRED == result && GL_WAIT_FAILED == result) { - break; - } + _plugin.withNonPresentThreadLock([&] { + while (!_queue.empty()) { + auto& front = _queue.front(); + auto result = glClientWaitSync(front.fence, 0, 0); + if (GL_TIMEOUT_EXPIRED == result && GL_WAIT_FAILED == result) { + break; + } - glDeleteSync(front.fence); - front.fence = 0; - _current = front; - _queue.pop(); - } + glDeleteSync(front.fence); + front.fence = 0; + _current = front; + _queue.pop(); + } + }); } void run() override { @@ -170,15 +171,28 @@ public: PoseData nextRender, nextSim; nextRender.frameIndex = _plugin.presentCount(); vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); - { - Lock lock(_plugin._presentMutex); - _presentCount++; - _presented.notify_one(); - _nextRender = nextRender; - _nextRender.update(_plugin._sensorResetMat); - _nextSim = nextSim; - _nextSim.update(_plugin._sensorResetMat); + + // Copy invalid poses in nextSim from nextRender + for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { + if (!nextSim.vrPoses[i].bPoseIsValid) { + nextSim.vrPoses[i] = nextRender.vrPoses[i]; + } } + + mat4 sensorResetMat; + _plugin.withNonPresentThreadLock([&] { + sensorResetMat = _plugin._sensorResetMat; + }); + + nextRender.update(sensorResetMat); + nextSim.update(sensorResetMat); + + _plugin.withNonPresentThreadLock([&] { + _nextRender = nextRender; + _nextSim = nextSim; + ++_presentCount; + _presented.notify_one(); + }); } _canvas.doneCurrent(); } @@ -366,19 +380,20 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } _currentRenderFrameInfo = FrameInfo(); + PoseData nextSimPoseData; withNonPresentThreadLock([&] { - _currentRenderFrameInfo.renderPose = _nextSimPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd]; + nextSimPoseData = _nextSimPoseData; }); // HACK: when interface is launched and steam vr is NOT running, openvr will return bad HMD poses for a few frames // To workaround this, filter out any hmd poses that are obviously bad, i.e. beneath the floor. - if (isBadPose(&_nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking)) { + if (isBadPose(&nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking)) { qDebug() << "WARNING: ignoring bad hmd pose from openvr"; // use the last known good HMD pose - _nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking = _lastGoodHMDPose; + nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking = _lastGoodHMDPose; } else { - _lastGoodHMDPose = _nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking; + _lastGoodHMDPose = nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking; } vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid }; @@ -387,7 +402,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2); // Find the left and right hand controllers, if they exist for (uint32_t i = 0; i < std::min(trackedCount, 2); ++i) { - if (_nextSimPoseData.vrPoses[i].bPoseIsValid) { + if (nextSimPoseData.vrPoses[i].bPoseIsValid) { auto role = _system->GetControllerRoleForTrackedDeviceIndex(controllerIndices[i]); if (vr::TrackedControllerRole_LeftHand == role) { handIndices[0] = controllerIndices[i]; @@ -398,8 +413,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } } - _currentRenderFrameInfo.renderPose = _nextSimPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd]; - + _currentRenderFrameInfo.renderPose = nextSimPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd]; bool keyboardVisible = isOpenVrKeyboardShown(); std::array handPoses; @@ -409,9 +423,9 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { continue; } auto deviceIndex = handIndices[i]; - const mat4& mat = _nextSimPoseData.poses[deviceIndex]; - const vec3& linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; - const vec3& angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; + const mat4& mat = nextSimPoseData.poses[deviceIndex]; + const vec3& linearVelocity = nextSimPoseData.linearVelocities[deviceIndex]; + const vec3& angularVelocity = nextSimPoseData.angularVelocities[deviceIndex]; auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity); static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 368b14cb1a..4279e6a6ac 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -66,8 +66,15 @@ struct PoseData { vec3 linearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 angularVelocities[vr::k_unMaxTrackedDeviceCount]; + PoseData() { + memset(vrPoses, 0, sizeof(vr::TrackedDevicePose_t) * vr::k_unMaxTrackedDeviceCount); + } + void update(const glm::mat4& resetMat) { for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + if (!vrPoses[i].bPoseIsValid) { + continue; + } poses[i] = resetMat * toGlm(vrPoses[i].mDeviceToAbsoluteTracking); linearVelocities[i] = transformVectorFast(resetMat, toGlm(vrPoses[i].vVelocity)); angularVelocities[i] = transformVectorFast(resetMat, toGlm(vrPoses[i].vAngularVelocity)); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index cf707c4d19..dc252afcf1 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -15,7 +15,6 @@ Script.load("system/users.js"); Script.load("system/mute.js"); Script.load("system/goto.js"); Script.load("system/hmd.js"); -Script.load("system/steam.js"); Script.load("system/marketplace.js"); Script.load("system/edit.js"); Script.load("system/mod.js"); @@ -26,5 +25,6 @@ Script.load("system/controllers/handControllerPointer.js"); Script.load("system/controllers/squeezeHands.js"); Script.load("system/controllers/grab.js"); Script.load("system/controllers/teleport.js"); +Script.load("system/controllers/toggleAdvancedMovementForHandControllers.js") Script.load("system/dialTone.js"); -Script.load("system/firstPersonHMD.js"); +Script.load("system/firstPersonHMD.js"); \ No newline at end of file diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 5a1ae7e5ee..77d0109b9f 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -37,7 +37,9 @@ var COLORS_TELEPORT_TOO_CLOSE = { blue: 73 }; -var TELEPORT_CANCEL_RANGE = 1.5; +var TELEPORT_CANCEL_RANGE = 1; +var USE_COOL_IN = true; +var COOL_IN_DURATION = 500; function ThumbPad(hand) { this.hand = hand; @@ -70,6 +72,8 @@ function Trigger(hand) { }; } +var coolInTimeout = null; + function Teleporter() { var _this = this; this.intersection = null; @@ -81,6 +85,8 @@ function Teleporter() { this.smoothArrivalInterval = null; this.teleportHand = null; this.tooClose = false; + this.inCoolIn = false; + this.initialize = function() { this.createMappings(); @@ -99,6 +105,7 @@ function Teleporter() { }; this.enterTeleportMode = function(hand) { + if (inTeleportMode === true) { return; } @@ -107,6 +114,14 @@ function Teleporter() { } inTeleportMode = true; + this.inCoolIn = true; + if (coolInTimeout !== null) { + Script.clearTimeout(coolInTimeout); + + } + coolInTimeout = Script.setTimeout(function() { + _this.inCoolIn = false; + }, COOL_IN_DURATION) if (this.smoothArrivalInterval !== null) { Script.clearInterval(this.smoothArrivalInterval); @@ -119,6 +134,9 @@ function Teleporter() { this.initialize(); Script.update.connect(this.update); this.updateConnected = true; + + + }; this.createTargetOverlay = function() { @@ -189,20 +207,19 @@ function Teleporter() { if (this.updateConnected === true) { Script.update.disconnect(this.update); } + this.disableMappings(); this.turnOffOverlayBeams(); - this.updateConnected = null; + this.inCoolIn = false; + inTeleportMode = false; Script.setTimeout(function() { - inTeleportMode = false; _this.enableGrab(); - }, 100); + }, 200); }; - - this.update = function() { if (isDisabled === 'both') { return; @@ -214,7 +231,13 @@ function Teleporter() { } teleporter.leftRay(); if ((leftPad.buttonValue === 0) && inTeleportMode === true) { - _this.teleport(); + if (_this.inCoolIn === true) { + _this.exitTeleportMode(); + _this.deleteTargetOverlay(); + _this.deleteCancelOverlay(); + } else { + _this.teleport(); + } return; } @@ -224,7 +247,13 @@ function Teleporter() { } teleporter.rightRay(); if ((rightPad.buttonValue === 0) && inTeleportMode === true) { - _this.teleport(); + if (_this.inCoolIn === true) { + _this.exitTeleportMode(); + _this.deleteTargetOverlay(); + _this.deleteCancelOverlay(); + } else { + _this.teleport(); + } return; } } @@ -235,7 +264,11 @@ function Teleporter() { var pose = Controller.getPoseValue(Controller.Standard.RightHand); var rightPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); var rightRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : - Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {x: 1, y: 0, z: 0})); + Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + })); var rightPickRay = { origin: rightPosition, @@ -260,15 +293,26 @@ function Teleporter() { this.createCancelOverlay(); } } else { - this.deleteCancelOverlay(); - - this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); - if (this.targetOverlay !== null) { - this.updateTargetOverlay(rightIntersection); + if (this.inCoolIn === true) { + this.deleteTargetOverlay(); + this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE); + if (this.cancelOverlay !== null) { + this.updateCancelOverlay(rightIntersection); + } else { + this.createCancelOverlay(); + } } else { - this.createTargetOverlay(); + this.deleteCancelOverlay(); + + this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); + if (this.targetOverlay !== null) { + this.updateTargetOverlay(rightIntersection); + } else { + this.createTargetOverlay(); + } } + } } else { @@ -283,7 +327,11 @@ function Teleporter() { var pose = Controller.getPoseValue(Controller.Standard.LeftHand); var leftPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); var leftRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : - Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {x: 1, y: 0, z: 0})); + Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + })); var leftPickRay = { origin: leftPosition, @@ -308,15 +356,26 @@ function Teleporter() { this.createCancelOverlay(); } } else { - this.deleteCancelOverlay(); - this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); - - if (this.targetOverlay !== null) { - this.updateTargetOverlay(leftIntersection); + if (this.inCoolIn === true) { + this.deleteTargetOverlay(); + this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE); + if (this.cancelOverlay !== null) { + this.updateCancelOverlay(leftIntersection); + } else { + this.createCancelOverlay(); + } } else { - this.createTargetOverlay(); + this.deleteCancelOverlay(); + this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT); + + if (this.targetOverlay !== null) { + this.updateTargetOverlay(leftIntersection); + } else { + this.createTargetOverlay(); + } } + } diff --git a/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js b/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js new file mode 100644 index 0000000000..3a75482770 --- /dev/null +++ b/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js @@ -0,0 +1,141 @@ +// Created by james b. pollack @imgntn on 8/18/2016 +// Copyright 2016 High Fidelity, Inc. +// +//advanced movements settings are in individual controller json files +//what we do is check the status of the 'advance movement' checkbox when you enter HMD mode +//if 'advanced movement' is checked...we give you the defaults that are in the json. +//if 'advanced movement' is not checked... we override the advanced controls with basic ones. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var mappingName, basicMapping, isChecked; + +var TURN_RATE = 1000; +var MENU_ITEM_NAME = "Advanced Movement For Hand Controllers"; +var SETTINGS_KEY = 'advancedMovementForHandControllersIsChecked'; +var previousSetting = Settings.getValue(SETTINGS_KEY); +if (previousSetting === '' || previousSetting === false || previousSetting === 'false') { + previousSetting = false; + isChecked = false; +} + +if (previousSetting === true || previousSetting === 'true') { + previousSetting = true; + isChecked = true; +} + +function addAdvancedMovementItemToSettingsMenu() { + Menu.addMenuItem({ + menuName: "Settings", + menuItemName: MENU_ITEM_NAME, + isCheckable: true, + isChecked: previousSetting + }); + +} + +function rotate180() { + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.angleAxis(180, { + x: 0, + y: 1, + z: 0 + })) + MyAvatar.orientation = newOrientation +} + +var inFlipTurn = false; + +function registerBasicMapping() { + mappingName = 'Hifi-AdvancedMovement-Dev-' + Math.random(); + basicMapping = Controller.newMapping(mappingName); + basicMapping.from(Controller.Standard.LY).to(function(value) { + var stick = Controller.getValue(Controller.Standard.LS); + if (value === 1 && Controller.Hardware.OculusTouch !== undefined) { + rotate180(); + } else if (Controller.Hardware.Vive !== undefined) { + if (value > 0.75 && inFlipTurn === false) { + inFlipTurn = true; + rotate180(); + Script.setTimeout(function() { + inFlipTurn = false; + }, TURN_RATE) + } + } + return; + }); + basicMapping.from(Controller.Standard.LX).to(Controller.Standard.RX); + basicMapping.from(Controller.Standard.RY).to(function(value) { + var stick = Controller.getValue(Controller.Standard.RS); + if (value === 1 && Controller.Hardware.OculusTouch !== undefined) { + rotate180(); + } else if (Controller.Hardware.Vive !== undefined) { + if (value > 0.75 && inFlipTurn === false) { + inFlipTurn = true; + rotate180(); + Script.setTimeout(function() { + inFlipTurn = false; + }, TURN_RATE) + } + } + return; + }) +} + + +function enableMappings() { + Controller.enableMapping(mappingName); +} + +function disableMappings() { + Controller.disableMapping(mappingName); +} + +function scriptEnding() { + Menu.removeMenuItem("Settings", MENU_ITEM_NAME); + disableMappings(); +} + + +function menuItemEvent(menuItem) { + if (menuItem == MENU_ITEM_NAME) { + isChecked = Menu.isOptionChecked(MENU_ITEM_NAME); + if (isChecked === true) { + Settings.setValue(SETTINGS_KEY, true); + disableMappings(); + } else if (isChecked === false) { + Settings.setValue(SETTINGS_KEY, false); + enableMappings(); + } + } +} + +addAdvancedMovementItemToSettingsMenu(); + +Script.scriptEnding.connect(scriptEnding); + +Menu.menuItemEvent.connect(menuItemEvent); + +registerBasicMapping(); + +Script.setTimeout(function() { + if (previousSetting === true) { + disableMappings(); + } else { + enableMappings(); + } +}, 100) + + +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + if (Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) { + if (isChecked === true) { + disableMappings(); + } else if (isChecked === false) { + enableMappings(); + } + + } + } +}); \ No newline at end of file diff --git a/scripts/system/steam.js b/unpublishedScripts/steam.js similarity index 100% rename from scripts/system/steam.js rename to unpublishedScripts/steam.js