diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 995a5bad27..ecdf14ebec 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -477,7 +477,7 @@ void EntityServer::startDynamicDomainVerification() { QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); QJsonObject request; request["certificate_id"] = i.key(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c2fe3af7c1..2d8bf7418a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -94,7 +94,7 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, root.insert(requestSubobjectKey, subobject); QJsonDocument doc { root }; - QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + metaversePath }; + QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + metaversePath }; QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -420,7 +420,7 @@ bool DomainServer::optionallySetupOAuth() { // if we don't have an oauth provider URL then we default to the default node auth url if (_oauthProviderURL.isEmpty()) { - _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL; + _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL(); } auto accountManager = DependencyManager::get(); @@ -2159,7 +2159,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonDocument doc(root); - QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/v1/places/" + place_id }; + QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/v1/places/" + place_id }; url.setQuery("access_token=" + accessTokenVariant->toString()); diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index f8bf1f62ae..3cf1e1450e 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -208,7 +208,7 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) { // send a request to the metaverse API for the public key for this domain auto& networkAccessManager = NetworkAccessManager::getInstance(); - QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL }; + QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL() }; QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID)); publicKeyURL.setPath(publicKeyPath); diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 300bcd46f0..0e2f2a5282 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -52,7 +52,11 @@ Item { targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height } - parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + var newWidth = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + if(!isNaN(newWidth)) { + parent.width = root.width = newWidth; + } + parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) + (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5022daf1cb..240f7cfcb9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -971,7 +971,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // set the account manager's root URL and trigger a login request if we don't have the access token accountManager->setIsAgent(true); - accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); auto addressManager = DependencyManager::get(); @@ -3257,8 +3257,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } } - - void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); @@ -4883,8 +4881,7 @@ void Application::update(float deltaTime) { if (_physicsEnabled) { { PROFILE_RANGE(simulation_physics, "PreStep"); - - PerformanceTimer perfTimer("updateStates)"); + PerformanceTimer perfTimer("preStep)"); static VectorOfMotionStates motionStates; _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); @@ -4917,22 +4914,22 @@ void Application::update(float deltaTime) { } { PROFILE_RANGE(simulation_physics, "Step"); - PerformanceTimer perfTimer("stepSimulation"); + PerformanceTimer perfTimer("step"); getEntities()->getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); } { PROFILE_RANGE(simulation_physics, "PostStep"); - PerformanceTimer perfTimer("harvestChanges"); + PerformanceTimer perfTimer("postStep"); if (_physicsEngine->hasOutgoingChanges()) { // grab the collision events BEFORE handleOutgoingChanges() because at this point // we have a better idea of which objects we own or should own. auto& collisionEvents = _physicsEngine->getCollisionEvents(); getEntities()->getTree()->withWriteLock([&] { - PROFILE_RANGE(simulation_physics, "Harvest"); - PerformanceTimer perfTimer("handleOutgoingChanges"); + PROFILE_RANGE(simulation_physics, "HandleChanges"); + PerformanceTimer perfTimer("handleChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); _entitySimulation->handleChangedMotionStates(outgoingChanges); @@ -4943,17 +4940,15 @@ void Application::update(float deltaTime) { }); if (!_aboutToQuit) { - // handleCollisionEvents() AFTER handleOutgoinChanges() + // handleCollisionEvents() AFTER handleOutgoingChanges() { PROFILE_RANGE(simulation_physics, "CollisionEvents"); - PerformanceTimer perfTimer("entities"); avatarManager->handleCollisionEvents(collisionEvents); // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _entitySimulation->handleCollisionEvents(collisionEvents); } - PROFILE_RANGE(simulation_physics, "UpdateEntities"); // NOTE: the getEntities()->update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). getEntities()->update(true); // update the models... @@ -4964,7 +4959,8 @@ void Application::update(float deltaTime) { myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } - if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && + if (PerformanceTimer::isActive() && + Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) { _physicsEngine->harvestPerformanceStats(); } @@ -7544,4 +7540,9 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { _avatarOverrideUrl = url; _saveAvatarOverrideUrl = save; } + +void Application::saveNextPhysicsStats(QString filename) { + _physicsEngine->saveNextPhysicsStats(filename); +} + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index 9542c5ccb6..ee16740f20 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -280,6 +280,7 @@ public: void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } + void saveNextPhysicsStats(QString filename); signals: void svoImportRequested(const QString& url); @@ -432,6 +433,7 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); + private: static void initDisplay(); void init(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4257462ff2..eeda9e9036 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -645,7 +645,8 @@ Menu::Menu() { // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false, + qApp, SLOT(enablePerfStats(bool))); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c874205611..02a1959a95 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -81,6 +81,7 @@ const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amaz const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; +const float MIN_SCALE_CHANGED_DELTA = 0.001f; MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), @@ -670,6 +671,11 @@ void MyAvatar::updateSensorToWorldMatrix() { glm::mat4 desiredMat = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), getWorldOrientation(), getWorldPosition()); _sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix); + bool hasSensorToWorldScaleChanged = false; + if (fabsf(getSensorToWorldScale() - sensorToWorldScale) > MIN_SCALE_CHANGED_DELTA) { + hasSensorToWorldScaleChanged = true; + } + lateUpdatePalms(); if (_enableDebugDrawSensorToWorldMatrix) { @@ -678,9 +684,13 @@ void MyAvatar::updateSensorToWorldMatrix() { } _sensorToWorldMatrixCache.set(_sensorToWorldMatrix); - updateJointFromController(controller::Action::LEFT_HAND, _controllerLeftHandMatrixCache); updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); + + if (hasSensorToWorldScaleChanged) { + emit sensorToWorldScaleChanged(sensorToWorldScale); + } + } // Update avatar head rotation with sensor data @@ -1405,6 +1415,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene()); _headBoneSet.clear(); emit skeletonChanged(); + } @@ -1440,6 +1451,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN UserActivityLogger::getInstance().changedModel("skeleton", urlString); } markIdentityDataChanged(); + } void MyAvatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index a707031167..f249be33ea 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -9,9 +9,12 @@ #include "MySkeletonModel.h" #include +#include #include "Application.h" #include "InterfaceLogging.h" +#include "AnimUtil.h" + MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) { } @@ -30,6 +33,39 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle }; } +static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { + glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor(); + glm::vec3 hipsPos = extractTranslation(hipsMat); + glm::quat hipsRot = glmExtractRotation(hipsMat); + + glm::mat4 avatarToWorldMat = myAvatar->getTransform().getMatrix(); + glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); + glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat; + + // dampen hips rotation, by mixing it with the avatar orientation in sensor space + const float MIX_RATIO = 0.5f; + hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); + + if (isFlying) { + // rotate the hips back to match the flying animation. + + const float TILT_ANGLE = 0.523f; + const glm::quat tiltRot = glm::angleAxis(TILT_ANGLE, transformVectorFast(avatarToSensorMat, -Vectors::UNIT_X)); + + glm::vec3 headPos; + int headIndex = myAvatar->getJointIndex("Head"); + if (headIndex != -1) { + headPos = transformPoint(avatarToSensorMat, myAvatar->getAbsoluteJointTranslationInObjectFrame(headIndex)); + } else { + headPos = transformPoint(myAvatar->getSensorToWorldMatrix(), myAvatar->getHMDSensorPosition()); + } + hipsRot = tiltRot * hipsRot; + hipsPos = headPos + tiltRot * (hipsPos - headPos); + } + + return AnimPose(hipsRot * Quaternions::Y_180, hipsPos); +} + // Called within Model::simulate call, below. void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { const FBXGeometry& geometry = getFBXGeometry(); @@ -124,6 +160,39 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } + // if hips are not under direct control, estimate the hips position. + if (avatarHeadPose.isValid() && !params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Hips]) { + bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); + + if (!_prevHipsValid) { + AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); + _prevHips = hips; + } + + AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); + + // smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation. + const float ROT_ALPHA = 0.9f; + const float TRANS_HORIZ_ALPHA = 0.9f; + const float TRANS_VERT_ALPHA = 0.1f; + float hipsY = hips.trans().y; + hips.trans() = lerp(hips.trans(), _prevHips.trans(), TRANS_HORIZ_ALPHA); + hips.trans().y = lerp(hipsY, _prevHips.trans().y, TRANS_VERT_ALPHA); + hips.rot() = safeLerp(hips.rot(), _prevHips.rot(), ROT_ALPHA); + + _prevHips = hips; + _prevHipsValid = true; + + glm::mat4 invRigMat = glm::inverse(myAvatar->getTransform().getMatrix() * Matrices::Y_180); + AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix()); + + params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; + params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Hips] = true; + + } else { + _prevHipsValid = false; + } + params.isTalking = head->getTimeWithoutTalking() <= 1.5f; // pass detailed torso k-dops to rig. diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index ad0ae1b8e9..d9f57a439a 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -25,6 +25,9 @@ public: private: void updateFingers(); + + AnimPose _prevHips; // sensor frame + bool _prevHipsValid { false }; }; #endif // hifi_MySkeletonModel_h diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index b0d293584c..3257a634c7 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -130,7 +130,7 @@ QString amountString(const QString& label, const QString&color, const QJsonValue return result + QString(""); } -static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace/items/"; +static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; void Ledger::historySuccess(QNetworkReply& reply) { // here we send a historyResult with some extra stuff in it // Namely, the styled text we'd like to show. The issue is the diff --git a/interface/src/networking/CloseEventSender.cpp b/interface/src/networking/CloseEventSender.cpp index de8bd897b2..fe939afe05 100644 --- a/interface/src/networking/CloseEventSender.cpp +++ b/interface/src/networking/CloseEventSender.cpp @@ -28,7 +28,7 @@ QNetworkRequest createNetworkRequest() { QNetworkRequest request; - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath(USER_ACTIVITY_URL); request.setUrl(requestURL); diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 21d257048c..5afbc2058a 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -23,10 +23,10 @@ static const float WEB_STYLUS_LENGTH = 0.2f; static const float TABLET_MIN_HOVER_DISTANCE = -0.1f; static const float TABLET_MAX_HOVER_DISTANCE = 0.1f; static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f; -static const float TABLET_MAX_TOUCH_DISTANCE = 0.01f; +static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f; static const float HOVER_HYSTERESIS = 0.01f; -static const float TOUCH_HYSTERESIS = 0.02f; +static const float TOUCH_HYSTERESIS = 0.001f; static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND; static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f; diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index d4b4ba1480..74a15db0ce 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -11,11 +11,12 @@ #include #include +#include #include #include -#include -#include #include +#include +#include #include "Application.h" @@ -141,6 +142,15 @@ void TestScriptingInterface::endTraceEvent(QString name) { tracing::traceEvent(trace_test(), name, tracing::DurationEnd); } +void TestScriptingInterface::savePhysicsSimulationStats(QString originalPath) { + QString path = FileUtils::replaceDateTimeTokens(originalPath); + path = FileUtils::computeDocumentPath(path); + if (!FileUtils::canCreateFile(path)) { + return; + } + qApp->saveNextPhysicsStats(path); +} + void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) { PROFILE_RANGE(script, name); fn.call(); diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 73b8f0ac93..aca07d110b 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -71,6 +71,11 @@ public slots: void endTraceEvent(QString name); + /**jsdoc + * Write detailed timing stats of next physics stepSimulation() to filename + */ + void savePhysicsSimulationStats(QString filename); + Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); private: diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index cab533cce3..66f208ca90 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -30,7 +30,7 @@ public: bool forwardEnabled() { return _forwardEnabled; } bool useFeed() { return _useFeed; } void setUseFeed(bool useFeed) { if (_useFeed != useFeed) { _useFeed = useFeed; emit useFeedChanged(); } } - QString metaverseServerUrl() { return NetworkingConstants::METAVERSE_SERVER_URL.toString(); } + QString metaverseServerUrl() { return NetworkingConstants::METAVERSE_SERVER_URL().toString(); } signals: void backEnabledChanged(); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e194551add..f991420fe8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -78,6 +78,8 @@ bool Stats::includeTimingRecord(const QString& name) { return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); } else if (name.startsWith("/paintGL/")) { return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); + } else if (name.startsWith("step/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming); } return true; } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 7ea3fa1129..16b771567c 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -289,7 +289,7 @@ void ContextOverlayInterface::openInspectionCertificate() { QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); QJsonObject request; request["certificate_id"] = entityProperties.getCertificateID(); @@ -359,7 +359,7 @@ void ContextOverlayInterface::openInspectionCertificate() { } } -static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace/items/"; +static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; void ContextOverlayInterface::openMarketplace() { // lets open the tablet and go to the current item in diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 78aa1f4ba8..44745c5c2d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1641,9 +1641,17 @@ void Rig::initAnimGraph(const QUrl& url) { // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); _animLoading = true; - connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) { + std::weak_ptr weakSkeletonPtr = _animSkeleton; + connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { _animNode = nodeIn; - _animNode->setSkeleton(_animSkeleton); + + // abort load if the previous skeleton was deleted. + auto sharedSkeletonPtr = weakSkeletonPtr.lock(); + if (!sharedSkeletonPtr) { + return; + } + + _animNode->setSkeleton(sharedSkeletonPtr); if (_userAnimState.clipNodeEnum != UserAnimState::None) { // restore the user animation we had before reset. @@ -1651,6 +1659,7 @@ void Rig::initAnimGraph(const QUrl& url) { _userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); } + // restore the role animations we had before reset. for (auto& roleAnimState : _roleAnimStates) { auto roleState = roleAnimState.second; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e738ad1c19..1ec4d9527f 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -31,7 +31,7 @@ class AnimInverseKinematics; // Rig instances are reentrant. // However only specific methods thread-safe. Noted below. -class Rig : public QObject, public std::enable_shared_from_this { +class Rig : public QObject { Q_OBJECT public: struct StateHandler { diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 476a8d4d88..672c0b69b3 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -96,9 +96,13 @@ void SoundProcessor::run() { QByteArray outputAudioByteArray; int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray); - if (sampleRate != 0) { - downSample(outputAudioByteArray, sampleRate); + if (sampleRate == 0) { + qCDebug(audio) << "Unsupported WAV file type"; + emit onError(300, "Failed to load sound file, reason: unsupported WAV file type"); + return; } + + downSample(outputAudioByteArray, sampleRate); } else if (fileName.endsWith(RAW_EXTENSION)) { // check if this was a stereo raw file // since it's raw the only way for us to know that is if the file was called .stereo.raw diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 5f7899ae74..8c3b56d89e 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -251,10 +251,10 @@ void EntityTreeRenderer::shutdown() { } void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction) { - PROFILE_RANGE_EX(simulation_physics, "Add", 0xffff00ff, (uint64_t)_entitiesToAdd.size()); + PROFILE_RANGE_EX(simulation_physics, "AddToScene", 0xffff00ff, (uint64_t)_entitiesToAdd.size()); PerformanceTimer pt("add"); - // Clear any expired entities - // FIXME should be able to use std::remove_if, but it fails due to some + // Clear any expired entities + // FIXME should be able to use std::remove_if, but it fails due to some // weird compilation error related to EntityItemID assignment operators for (auto itr = _entitiesToAdd.begin(); _entitiesToAdd.end() != itr; ) { if (itr->second.expired()) { @@ -272,7 +272,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r continue; } - // Path to the parent transforms is not valid, + // Path to the parent transforms is not valid, // don't add to the scene graph yet if (!entity->isParentPathComplete()) { continue; @@ -296,7 +296,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { - PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size()); + PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size()); PerformanceTimer pt("change"); std::unordered_set changedEntities; _changedEntitiesGuard.withWriteLock([&] { @@ -402,6 +402,8 @@ void EntityTreeRenderer::update(bool simulate) { PerformanceTimer perfTimer("ETRupdate"); if (_tree && !_shuttingDown) { EntityTreePointer tree = std::static_pointer_cast(_tree); + + // here we update _currentFrame and _lastAnimated and sync with the server properties. tree->update(simulate); // Update the rendereable entities as needed @@ -736,7 +738,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { PickRay ray = _viewState->computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects && rayPickResult.entity) { - //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; + // qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID, diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c992eb5dc4..e578e4858d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -63,13 +63,17 @@ bool ModelEntityWrapper::isModelLoaded() const { EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; } RenderableModelEntityItem::RenderableModelEntityItem(const EntityItemID& entityItemID, bool dimensionsInitialized) : ModelEntityWrapper(entityItemID), _dimensionsInitialized(dimensionsInitialized) { + + } RenderableModelEntityItem::~RenderableModelEntityItem() { } @@ -464,7 +468,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setParams(type, dimensions, getCompoundShapeURL()); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { // TODO: assert we never fall in here when model not fully loaded - //assert(_model && _model->isLoaded()); + // assert(_model && _model->isLoaded()); updateModelBounds(); model->updateGeometry(); @@ -974,9 +978,6 @@ void ModelEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entit entity->setModel({}); } -bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) { - return !(a == b); -} void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { if (!_animation || !_animation->isLoaded()) { @@ -991,19 +992,12 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { return; } - if (!_lastAnimated) { - _lastAnimated = usecTimestampNow(); - return; - } - - auto now = usecTimestampNow(); - auto interval = now - _lastAnimated; - _lastAnimated = now; - float deltaTime = (float)interval / (float)USECS_PER_SECOND; - _currentFrame += (deltaTime * _renderAnimationProperties.getFPS()); - { - int animationCurrentFrame = (int)(glm::floor(_currentFrame)) % frameCount; + // the current frame is set on the server in update() in ModelEntityItem.cpp + int animationCurrentFrame = (int)(glm::floor(entity->getAnimationCurrentFrame())); + + // in the case where the last frame is greater than the framecount then clamp + // it to the end of the animation until it loops around. if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { animationCurrentFrame = 0; } @@ -1039,10 +1033,10 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { glm::mat4 translationMat; if (allowTranslation) { - if(index < translations.size()){ + if (index < translations.size()) { translationMat = glm::translate(translations[index]); } - } else if (index < animationJointNames.size()){ + } else if (index < animationJointNames.size()) { QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation if (originalFbxIndices.contains(jointName)) { @@ -1317,25 +1311,17 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (model->getRenderItemsNeedUpdate()) { model->updateRenderItems(); } - - { - DETAILED_PROFILE_RANGE(simulation_physics, "CheckAnimation"); - // make a copy of the animation properites - auto newAnimationProperties = entity->getAnimationProperties(); - if (newAnimationProperties != _renderAnimationProperties) { - withWriteLock([&] { - _renderAnimationProperties = newAnimationProperties; - _currentFrame = _renderAnimationProperties.getCurrentFrame(); - }); - } - } - + + // The code to deal with the change of properties is now in ModelEntityItem.cpp + // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); } - animate(entity); + if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) { + animate(entity); + } emit requestRenderUpdate(); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 0272bed575..b4f2665692 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -33,7 +33,7 @@ namespace render { namespace entities { class ModelEntityRenderer; } } -//#define MODEL_ENTITY_USE_FADE_EFFECT +// #define MODEL_ENTITY_USE_FADE_EFFECT class ModelEntityWrapper : public ModelEntityItem { using Parent = ModelEntityItem; friend class render::entities::ModelEntityRenderer; @@ -133,7 +133,7 @@ class ModelEntityRenderer : public TypedEntityRenderer #include #include +#include #include #include // usecTimestampNow() #include @@ -984,6 +985,7 @@ void EntityItem::setCollisionSoundURL(const QString& value) { } void EntityItem::simulate(const quint64& now) { + DETAILED_PROFILE_RANGE(simulation_physics, "Simulate"); if (getLastSimulated() == 0) { setLastSimulated(now); } @@ -1039,6 +1041,7 @@ void EntityItem::simulate(const quint64& now) { } bool EntityItem::stepKinematicMotion(float timeElapsed) { + DETAILED_PROFILE_RANGE(simulation_physics, "StepKinematicMotion"); // get all the data Transform transform; glm::vec3 linearVelocity; @@ -2840,7 +2843,7 @@ void EntityItem::retrieveMarketplacePublicKey() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/marketplace_key"); QJsonObject request; networkRequest.setUrl(requestURL); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 9f7ba1cc80..c7b08f2110 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index f91d728d78..36b0d8ab2d 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -29,7 +29,6 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { } void EntitySimulation::updateEntities() { - PROFILE_RANGE(simulation_physics, "ES::updateEntities"); QMutexLocker lock(&_mutex); quint64 now = usecTimestampNow(); @@ -38,12 +37,7 @@ void EntitySimulation::updateEntities() { callUpdateOnEntitiesThatNeedIt(now); moveSimpleKinematics(now); updateEntitiesInternal(now); - - { - PROFILE_RANGE(simulation_physics, "Sort"); - PerformanceTimer perfTimer("sortingEntities"); - sortEntitiesThatMoved(); - } + sortEntitiesThatMoved(); } void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { @@ -101,6 +95,7 @@ void EntitySimulation::changeEntityInternal(EntityItemPointer entity) { // protected void EntitySimulation::expireMortalEntities(const quint64& now) { if (now > _nextExpiry) { + PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one _nextExpiry = quint64(-1); QMutexLocker lock(&_mutex); @@ -146,6 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { // protected void EntitySimulation::sortEntitiesThatMoved() { + PROFILE_RANGE_EX(simulation_physics, "SortTree", 0xffff00ff, (uint64_t)_entitiesToSort.size()); // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. MovingEntitiesOperator moveOperator; @@ -265,7 +261,7 @@ void EntitySimulation::clearEntities() { } void EntitySimulation::moveSimpleKinematics(const quint64& now) { - PROFILE_RANGE_EX(simulation_physics, "Kinematics", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); + PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { EntityItemPointer entity = *itemItr; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e62399ce95..b5765bb44b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1308,7 +1308,7 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); QJsonObject request; request["certificate_id"] = certID; @@ -1770,24 +1770,26 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { } void EntityTree::update(bool simulate) { - PROFILE_RANGE(simulation_physics, "ET::update"); + PROFILE_RANGE(simulation_physics, "UpdateTree"); fixupNeedsParentFixups(); if (simulate && _simulation) { withWriteLock([&] { _simulation->updateEntities(); - VectorOfEntities pendingDeletes; - _simulation->takeEntitiesToDelete(pendingDeletes); + { + PROFILE_RANGE(simulation_physics, "Deletes"); + VectorOfEntities pendingDeletes; + _simulation->takeEntitiesToDelete(pendingDeletes); + if (pendingDeletes.size() > 0) { + // translate into list of ID's + QSet idsToDelete; - if (pendingDeletes.size() > 0) { - // translate into list of ID's - QSet idsToDelete; + for (auto entity : pendingDeletes) { + idsToDelete.insert(entity->getEntityItemID()); + } - for (auto entity : pendingDeletes) { - idsToDelete.insert(entity->getEntityItemID()); + // delete these things the roundabout way + deleteEntities(idsToDelete, true); } - - // delete these things the roundabout way - deleteEntities(idsToDelete, true); } }); } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 313721ff54..323584c7ee 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -33,6 +33,8 @@ EntityItemPointer ModelEntityItem::factory(const EntityItemID& entityID, const E ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _lastAnimated = usecTimestampNow(); + // set the last animated when interface (re)starts _type = EntityTypes::Model; _lastKnownCurrentFrame = -1; _color[0] = _color[1] = _color[2] = 0; @@ -186,6 +188,105 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit } + +// added update function back for property fix +void ModelEntityItem::update(const quint64& now) { + + { + auto currentAnimationProperties = this->getAnimationProperties(); + + if (_previousAnimationProperties != currentAnimationProperties) { + withWriteLock([&] { + // if we hit start animation or change the first or last frame then restart the animation + if ((currentAnimationProperties.getFirstFrame() != _previousAnimationProperties.getFirstFrame()) || + (currentAnimationProperties.getLastFrame() != _previousAnimationProperties.getLastFrame()) || + (currentAnimationProperties.getRunning() && !_previousAnimationProperties.getRunning())) { + + // when we start interface and the property is are set then the current frame is initialized to -1 + if (_currentFrame < 0) { + // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set + _currentFrame = currentAnimationProperties.getCurrentFrame(); + setAnimationCurrentFrame(_currentFrame); + } else { + _lastAnimated = usecTimestampNow(); + _currentFrame = currentAnimationProperties.getFirstFrame(); + setAnimationCurrentFrame(currentAnimationProperties.getFirstFrame()); + } + } else if (!currentAnimationProperties.getRunning() && _previousAnimationProperties.getRunning()) { + _currentFrame = currentAnimationProperties.getFirstFrame(); + setAnimationCurrentFrame(_currentFrame); + } else if (currentAnimationProperties.getCurrentFrame() != _previousAnimationProperties.getCurrentFrame()) { + // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated + _currentFrame = currentAnimationProperties.getCurrentFrame(); + } + + }); + _previousAnimationProperties = this->getAnimationProperties(); + + } + + if (isAnimatingSomething()) { + if (!(getAnimationFirstFrame() < 0) && !(getAnimationFirstFrame() > getAnimationLastFrame())) { + updateFrameCount(); + } + } + } + + EntityItem::update(now); +} + +bool ModelEntityItem::needsToCallUpdate() const { + + return true; +} + +void ModelEntityItem::updateFrameCount() { + + if (_currentFrame < 0.0f) { + return; + } + + if (!_lastAnimated) { + _lastAnimated = usecTimestampNow(); + return; + } + + auto now = usecTimestampNow(); + + // update the interval since the last animation. + auto interval = now - _lastAnimated; + _lastAnimated = now; + + // if fps is negative then increment timestamp and return. + if (getAnimationFPS() < 0.0f) { + return; + } + + int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1; + + if (!getAnimationHold() && getAnimationIsPlaying()) { + float deltaTime = (float)interval / (float)USECS_PER_SECOND; + _currentFrame += (deltaTime * getAnimationFPS()); + if (_currentFrame > getAnimationLastFrame()) { + if (getAnimationLoop()) { + _currentFrame = getAnimationFirstFrame() + (int)(glm::floor(_currentFrame - getAnimationFirstFrame())) % (updatedFrameCount - 1); + } else { + _currentFrame = getAnimationLastFrame(); + } + } else if (_currentFrame < getAnimationFirstFrame()) { + if (getAnimationFirstFrame() < 0) { + _currentFrame = 0; + } else { + _currentFrame = getAnimationFirstFrame(); + } + } + // qCDebug(entities) << "in update frame " << _currentFrame; + setAnimationCurrentFrame(_currentFrame); + } + + +} + void ModelEntityItem::debugDump() const { qCDebug(entities) << "ModelEntityItem id:" << getEntityItemID(); qCDebug(entities) << " edited ago:" << getEditedAgo(); @@ -538,6 +639,13 @@ void ModelEntityItem::setAnimationLoop(bool loop) { }); } +bool ModelEntityItem::getAnimationLoop() const { + return resultWithReadLock([&] { + return _animationProperties.getLoop(); + }); +} + + void ModelEntityItem::setAnimationHold(bool hold) { withWriteLock([&] { _animationProperties.setHold(hold); @@ -573,8 +681,9 @@ float ModelEntityItem::getAnimationLastFrame() const { return _animationProperties.getLastFrame(); }); } + bool ModelEntityItem::getAnimationIsPlaying() const { - return resultWithReadLock([&] { + return resultWithReadLock([&] { return _animationProperties.getRunning(); }); } @@ -585,8 +694,15 @@ float ModelEntityItem::getAnimationCurrentFrame() const { }); } -bool ModelEntityItem::isAnimatingSomething() const { +float ModelEntityItem::getAnimationFPS() const { return resultWithReadLock([&] { + return _animationProperties.getFPS(); + }); +} + + +bool ModelEntityItem::isAnimatingSomething() const { + return resultWithReadLock([&] { return !_animationProperties.getURL().isEmpty() && _animationProperties.getRunning() && (_animationProperties.getFPS() != 0.0f); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 2c3ef3aa2d..7fee022011 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -17,6 +17,7 @@ #include #include "AnimationPropertyGroup.h" + class ModelEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -46,8 +47,11 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - //virtual void update(const quint64& now) override; - //virtual bool needsToCallUpdate() const override; + // update() and needstocallupdate() added back for the entity property fix + virtual void update(const quint64& now) override; + virtual bool needsToCallUpdate() const override; + void updateFrameCount(); + virtual void debugDump() const override; void setShapeType(ShapeType type) override; @@ -90,6 +94,7 @@ public: bool getAnimationAllowTranslation() const { return _animationProperties.getAllowTranslation(); }; void setAnimationLoop(bool loop); + bool getAnimationLoop() const; void setAnimationHold(bool hold); bool getAnimationHold() const; @@ -102,6 +107,7 @@ public: bool getAnimationIsPlaying() const; float getAnimationCurrentFrame() const; + float getAnimationFPS() const; bool isAnimatingSomething() const; static const QString DEFAULT_TEXTURES; @@ -147,7 +153,7 @@ protected: }; QVector _localJointData; - int _lastKnownCurrentFrame; + int _lastKnownCurrentFrame{-1}; rgbColor _color; QString _modelURL; @@ -160,6 +166,11 @@ protected: QString _textures; ShapeType _shapeType = SHAPE_TYPE_NONE; + +private: + uint64_t _lastAnimated{ 0 }; + AnimationPropertyGroup _previousAnimationProperties; + float _currentFrame{ -1.0f }; }; #endif // hifi_ModelEntityItem_h diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index e0c2efd72e..315c6a86d2 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -56,7 +56,7 @@ float OBJTokenizer::getFloat() { return std::stof((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data()); } -int OBJTokenizer::nextToken() { +int OBJTokenizer::nextToken(bool allowSpaceChar /*= false*/) { if (_pushedBackToken != NO_PUSHBACKED_TOKEN) { int token = _pushedBackToken; _pushedBackToken = NO_PUSHBACKED_TOKEN; @@ -93,7 +93,7 @@ int OBJTokenizer::nextToken() { _datum = ""; _datum.append(ch); while (_device->getChar(&ch)) { - if (QChar(ch).isSpace() || ch == '\"') { + if ((QChar(ch).isSpace() || ch == '\"') && (!allowSpaceChar || ch != ' ')) { ungetChar(ch); // read until we encounter a special character, then replace it break; } @@ -399,7 +399,7 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi currentMaterialName = QString("part-") + QString::number(_partCounter++); } } else if (token == "mtllib" && !_url.isEmpty()) { - if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { + if (tokenizer.nextToken(true) != OBJTokenizer::DATUM_TOKEN) { break; } QByteArray libraryName = tokenizer.getDatum(); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index fb250833cf..45e3f79480 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -11,7 +11,7 @@ public: DATUM_TOKEN = 0x100, COMMENT_TOKEN = 0x101 }; - int nextToken(); + int nextToken(bool allowSpaceChar = false); const QByteArray& getDatum() const { return _datum; } bool isNextTokenFloat(); const QByteArray getLineAsDatum(); // some "filenames" have spaces in them diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 7d97687d0b..87b17d00d5 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -97,7 +97,7 @@ public: void setTemporaryDomain(const QUuid& domainID, const QString& key); const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } - QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL; } + QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL(); } public slots: void requestAccessToken(const QString& login, const QString& password); diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 0c210e4360..a4726f9b1a 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -12,15 +12,31 @@ #ifndef hifi_NetworkingConstants_h #define hifi_NetworkingConstants_h +#include #include namespace NetworkingConstants { // If you want to use STAGING instead of STABLE, - // don't forget to ALSO change the Domain Server Metaverse Server URL inside of: + // links from the Domain Server web interface (like the connect account token generation) + // will still point at stable unless you ALSO change the Domain Server Metaverse Server URL inside of: // \domain-server\resources\web\js\shared.js + + // You can avoid changing that and still effectively use a connected domain on staging + // if you manually generate a personal access token for the domains scope + // at https://staging.highfidelity.com/user/tokens/new?for_domain_server=true + const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com"); const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com"); - const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE; + + // You can change the return of this function if you want to use a custom metaverse URL at compile time + // or you can pass a custom URL via the env variable + static const QUrl METAVERSE_SERVER_URL() { + static const QString HIFI_METAVERSE_URL_ENV = "HIFI_METAVERSE_URL"; + static const QUrl serverURL = QProcessEnvironment::systemEnvironment().contains(HIFI_METAVERSE_URL_ENV) + ? QUrl(QProcessEnvironment::systemEnvironment().value(HIFI_METAVERSE_URL_ENV)) + : METAVERSE_SERVER_URL_STABLE; + return serverURL; + }; } #endif // hifi_NetworkingConstants_h diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index 15d5acbc67..a30786efa4 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -35,7 +35,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O auto accountManager = DependencyManager::get(); if (accountManager->hasValidAccessToken() - && req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) { + && req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL().host()) { QNetworkRequest authenticatedRequest(req); authenticatedRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 7e8b431ceb..a688d521d6 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -14,8 +14,9 @@ #include #include #include -#include #include +#include +#include #include "BulletUtil.h" #include "EntityMotionState.h" @@ -325,6 +326,7 @@ bool EntityMotionState::isCandidateForOwnership() const { } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // NOTE: we only get here if we think we own the simulation assert(_body); @@ -476,6 +478,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. assert(_entity); @@ -516,6 +519,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Send"); assert(_entity); assert(entityTreeIsLocked()); @@ -731,6 +735,7 @@ void EntityMotionState::resetMeasuredBodyAcceleration() { } void EntityMotionState::measureBodyAcceleration() { + DETAILED_PROFILE_RANGE(simulation_physics, "MeasureAccel"); // try to manually measure the true acceleration of the object uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); uint32_t numSubsteps = thisStep - _lastMeasureStep; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 3e87b9437d..e4ba47e205 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -10,12 +10,14 @@ // +#include "PhysicalEntitySimulation.h" + +#include #include "PhysicsHelpers.h" #include "PhysicsLogging.h" #include "ShapeManager.h" -#include "PhysicalEntitySimulation.h" PhysicalEntitySimulation::PhysicalEntitySimulation() { } @@ -274,20 +276,24 @@ void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotio } void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionStates& motionStates) { + PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); // walk the motionStates looking for those that correspond to entities - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - EntityItemPointer entity = entityState->getEntity(); - assert(entity.get()); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + { + PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + EntityItemPointer entity = entityState->getEntity(); + assert(entity.get()); + if (entityState->isCandidateForOwnership()) { + _outgoingChanges.insert(entityState); + } + _entitiesToSort.insert(entity); } - _entitiesToSort.insert(entity); } } @@ -302,6 +308,7 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta } // look for entities to prune or update + PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a64796308e..fe794772e2 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -11,7 +11,12 @@ #include +#include + +#include + #include +#include #include "CharacterController.h" #include "ObjectMotionState.h" @@ -290,6 +295,7 @@ void PhysicsEngine::stepSimulation() { float timeStep = btMin(dt, MAX_TIMESTEP); if (_myAvatarController) { + DETAILED_PROFILE_RANGE(simulation_physics, "avatarController"); BT_PROFILE("avatarController"); // TODO: move this stuff outside and in front of stepSimulation, because // the updateShapeIfNecessary() call needs info from MyAvatar and should @@ -328,45 +334,107 @@ void PhysicsEngine::stepSimulation() { } } +class CProfileOperator { +public: + CProfileOperator() {} + void recurse(CProfileIterator* itr, QString context) { + // The context string will get too long if we accumulate it properly + //QString newContext = context + QString("/") + itr->Get_Current_Parent_Name(); + // so we use this four-character indentation + QString newContext = context + QString(".../"); + process(itr, newContext); + + // count the children + int32_t numChildren = 0; + itr->First(); + while (!itr->Is_Done()) { + itr->Next(); + ++numChildren; + } + + // recurse the children + if (numChildren > 0) { + // recurse the children + for (int32_t i = 0; i < numChildren; ++i) { + itr->Enter_Child(i); + recurse(itr, newContext); + } + } + // retreat back to parent + itr->Enter_Parent(); + } + virtual void process(CProfileIterator*, QString context) = 0; +}; + +class StatsHarvester : public CProfileOperator { +public: + StatsHarvester() {} + void process(CProfileIterator* itr, QString context) override { + QString name = context + itr->Get_Current_Parent_Name(); + uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time()); + PerformanceTimer::addTimerRecord(name, time); + }; +}; + +class StatsWriter : public CProfileOperator { +public: + StatsWriter(QString filename) : _file(filename) { + _file.open(QFile::WriteOnly); + if (_file.error() != QFileDevice::NoError) { + qCDebug(physics) << "unable to open file " << _file.fileName() << " to save stepSimulation() stats"; + } + } + ~StatsWriter() { + _file.close(); + } + void process(CProfileIterator* itr, QString context) override { + QString name = context + itr->Get_Current_Parent_Name(); + float time = (btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time(); + if (_file.error() == QFileDevice::NoError) { + QTextStream s(&_file); + s << name << " = " << time << "\n"; + } + } +protected: + QFile _file; +}; + void PhysicsEngine::harvestPerformanceStats() { // unfortunately the full context names get too long for our stats presentation format //QString contextName = PerformanceTimer::getContextName(); // TODO: how to show full context name? QString contextName("..."); - CProfileIterator* profileIterator = CProfileManager::Get_Iterator(); - if (profileIterator) { + CProfileIterator* itr = CProfileManager::Get_Iterator(); + if (itr) { // hunt for stepSimulation context - profileIterator->First(); - for (int32_t childIndex = 0; !profileIterator->Is_Done(); ++childIndex) { - if (QString(profileIterator->Get_Current_Name()) == "stepSimulation") { - profileIterator->Enter_Child(childIndex); - recursivelyHarvestPerformanceStats(profileIterator, contextName); + itr->First(); + for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) { + if (QString(itr->Get_Current_Name()) == "stepSimulation") { + itr->Enter_Child(childIndex); + StatsHarvester harvester; + harvester.recurse(itr, "step/"); break; } - profileIterator->Next(); + itr->Next(); } } } -void PhysicsEngine::recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName) { - QString parentContextName = contextName + QString("/") + QString(profileIterator->Get_Current_Parent_Name()); - // get the stats for the children - int32_t numChildren = 0; - profileIterator->First(); - while (!profileIterator->Is_Done()) { - QString childContextName = parentContextName + QString("/") + QString(profileIterator->Get_Current_Name()); - uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * profileIterator->Get_Current_Total_Time()); - PerformanceTimer::addTimerRecord(childContextName, time); - profileIterator->Next(); - ++numChildren; +void PhysicsEngine::printPerformanceStatsToFile(const QString& filename) { + CProfileIterator* itr = CProfileManager::Get_Iterator(); + if (itr) { + // hunt for stepSimulation context + itr->First(); + for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) { + if (QString(itr->Get_Current_Name()) == "stepSimulation") { + itr->Enter_Child(childIndex); + StatsWriter writer(filename); + writer.recurse(itr, ""); + break; + } + itr->Next(); + } } - // recurse the children - for (int32_t i = 0; i < numChildren; ++i) { - profileIterator->Enter_Child(i); - recursivelyHarvestPerformanceStats(profileIterator, contextName); - } - // retreat back to parent - profileIterator->Enter_Parent(); } void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { @@ -399,6 +467,7 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const } void PhysicsEngine::updateContactMap() { + DETAILED_PROFILE_RANGE(simulation_physics, "updateContactMap"); BT_PROFILE("updateContactMap"); ++_numContactFrames; @@ -515,10 +584,21 @@ const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { void PhysicsEngine::dumpStatsIfNecessary() { if (_dumpNextStats) { _dumpNextStats = false; + CProfileManager::Increment_Frame_Counter(); + if (_saveNextStats) { + _saveNextStats = false; + printPerformanceStatsToFile(_statsFilename); + } CProfileManager::dumpAll(); } } +void PhysicsEngine::saveNextPhysicsStats(QString filename) { + _saveNextStats = true; + _dumpNextStats = true; + _statsFilename = filename; +} + // Bullet collision flags are as follows: // CF_STATIC_OBJECT= 1, // CF_KINEMATIC_OBJECT= 2, diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 3063a4a89a..6619a5489d 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -62,6 +62,7 @@ public: void stepSimulation(); void harvestPerformanceStats(); + void printPerformanceStatsToFile(const QString& filename); void updateContactMap(); bool hasOutgoingChanges() const { return _hasOutgoingChanges; } @@ -76,6 +77,9 @@ public: /// \brief prints timings for last frame if stats have been requested. void dumpStatsIfNecessary(); + /// \brief saves timings for last frame in filename + void saveNextPhysicsStats(QString filename); + /// \param offset position of simulation origin in domain-frame void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; } @@ -94,7 +98,6 @@ public: private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); - void recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName); /// \brief bump any objects that touch this one, then remove contact info void bumpAndPruneContacts(ObjectMotionState* motionState); @@ -116,6 +119,7 @@ private: QHash _objectDynamics; QHash> _objectDynamicsByBody; std::set _activeStaticBodies; + QString _statsFilename; glm::vec3 _originOffset; @@ -124,8 +128,9 @@ private: uint32_t _numContactFrames = 0; uint32_t _numSubsteps; - bool _dumpNextStats = false; - bool _hasOutgoingChanges = false; + bool _dumpNextStats { false }; + bool _saveNextStats { false }; + bool _hasOutgoingChanges { false }; }; diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 24cfbc2609..5b8c0d5843 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -18,6 +18,7 @@ #include #include "ThreadSafeDynamicsWorld.h" +#include "Profile.h" ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( btDispatcher* dispatcher, @@ -29,6 +30,7 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep, SubStepCallback onSubStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "stepWithCB"); BT_PROFILE("stepSimulationWithSubstepCallback"); int subSteps = 0; if (maxSubSteps) { @@ -68,11 +70,13 @@ int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep saveKinematicState(fixedTimeStep*clampedSimulationSteps); { + DETAILED_PROFILE_RANGE(simulation_physics, "applyGravity"); BT_PROFILE("applyGravity"); applyGravity(); } for (int i=0;i_scene->getStage(); if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); - batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer()); + if (hazePointer) { + batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer()); + } else { + // Something is wrong, so just quit Haze + return; + } } batch.setUniformBuffer(HazeEffect_TransformBufferSlot, transformBuffer->getFrameTransformBuffer()); @@ -178,7 +183,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu if (lightStage) { model::LightPointer keyLight; keyLight = lightStage->getCurrentKeyLight(); - if (keyLight != nullptr) { + if (keyLight) { batch.setUniformBuffer(HazeEffect_LightingMapSlot, keyLight->getLightSchemaBuffer()); } } diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 1d3c8fda32..62384f9d97 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -22,7 +22,7 @@ #include "ScriptEngine.h" #include "XMLHttpRequestClass.h" -const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/"; +const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/"; Q_DECLARE_METATYPE(QByteArray*) diff --git a/libraries/shared/src/Trace.cpp b/libraries/shared/src/Trace.cpp index d7feb65ff3..3f6a2dd643 100644 --- a/libraries/shared/src/Trace.cpp +++ b/libraries/shared/src/Trace.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -31,6 +30,8 @@ #include "Gzip.h" #include "PortableHighResolutionClock.h" +#include "SharedLogging.h" +#include "shared/FileUtils.h" #include "shared/GlobalAppProperties.h" using namespace tracing; @@ -104,30 +105,13 @@ void TraceEvent::writeJson(QTextStream& out) const { #endif } -void Tracer::serialize(const QString& originalPath) { - - QString path = originalPath; - - // Filter for specific tokens potentially present in the path: - auto now = QDateTime::currentDateTime(); - - path = path.replace("{DATE}", now.date().toString("yyyyMMdd")); - path = path.replace("{TIME}", now.time().toString("HHmm")); - - // If the filename is relative, turn it into an absolute path relative to the document directory. - QFileInfo originalFileInfo(path); - if (originalFileInfo.isRelative()) { - QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - path = docsLocation + "/" + path; - QFileInfo info(path); - if (!info.absoluteDir().exists()) { - QString originalRelativePath = originalFileInfo.path(); - QDir(docsLocation).mkpath(originalRelativePath); - } +void Tracer::serialize(const QString& filename) { + QString fullPath = FileUtils::replaceDateTimeTokens(filename); + fullPath = FileUtils::computeDocumentPath(fullPath); + if (!FileUtils::canCreateFile(fullPath)) { + return; } - - std::list currentEvents; { std::lock_guard guard(_eventsMutex); @@ -137,11 +121,6 @@ void Tracer::serialize(const QString& originalPath) { } } - // If the file exists and we can't remove it, fail early - if (QFileInfo(path).exists() && !QFile::remove(path)) { - return; - } - // If we can't open a temp file for writing, fail early QByteArray data; { @@ -159,15 +138,16 @@ void Tracer::serialize(const QString& originalPath) { out << "\n]"; } - if (path.endsWith(".gz")) { + if (fullPath.endsWith(".gz")) { QByteArray compressed; gzip(data, compressed); data = compressed; - } - + } + { - QFile file(path); + QFile file(fullPath); if (!file.open(QIODevice::WriteOnly)) { + qDebug(shared) << "failed to open file '" << fullPath << "'"; return; } file.write(data); @@ -191,7 +171,6 @@ void Tracer::serialize(const QString& originalPath) { } } } }; - data = document.toJson(QJsonDocument::Compact); } #endif diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 8c962dfd6d..dba0af7b16 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -12,6 +12,7 @@ #include "FileUtils.h" +#include #include #include #include @@ -20,6 +21,8 @@ #include #include +#include "../SharedLogging.h" + QString FileUtils::readFile(const QString& filename) { QFile file(filename); @@ -82,20 +85,54 @@ QString FileUtils::standardPath(QString subfolder) { // standard path // Mac: ~/Library/Application Support/Interface QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - if (!subfolder.startsWith("/")) { subfolder.prepend("/"); } - if (!subfolder.endsWith("/")) { subfolder.append("/"); } - path.append(subfolder); QDir logDir(path); if (!logDir.exists(path)) { logDir.mkpath(path); } - return path; } + +QString FileUtils::replaceDateTimeTokens(const QString& originalPath) { + // Filter for specific tokens potentially present in the path: + auto now = QDateTime::currentDateTime(); + QString path = originalPath; + path.replace("{DATE}", now.date().toString("yyyyMMdd")); + path.replace("{TIME}", now.time().toString("HHmm")); + return path; +} + + +QString FileUtils::computeDocumentPath(const QString& originalPath) { + // If the filename is relative, turn it into an absolute path relative to the document directory. + QString path = originalPath; + QFileInfo originalFileInfo(originalPath); + if (originalFileInfo.isRelative()) { + QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + path = docsLocation + "/" + originalPath; + } + return path; +} + +bool FileUtils::canCreateFile(const QString& fullPath) { + // If the file exists and we can't remove it, fail early + QFileInfo fileInfo(fullPath); + if (fileInfo.exists() && !QFile::remove(fullPath)) { + qDebug(shared) << "unable to overwrite file '" << fullPath << "'"; + return false; + } + QDir dir(fileInfo.absolutePath()); + if (!dir.exists()) { + if (!dir.mkpath(fullPath)) { + qDebug(shared) << "unable to create dir '" << dir.absolutePath() << "'"; + return false; + } + } + return true; +} diff --git a/libraries/shared/src/shared/FileUtils.h b/libraries/shared/src/shared/FileUtils.h index 4f2c1b7af5..d68fcd8a44 100644 --- a/libraries/shared/src/shared/FileUtils.h +++ b/libraries/shared/src/shared/FileUtils.h @@ -21,6 +21,9 @@ public: static QString standardPath(QString subfolder); static QString readFile(const QString& filename); static QStringList readLines(const QString& filename, QString::SplitBehavior splitBehavior = QString::KeepEmptyParts); + static QString replaceDateTimeTokens(const QString& path); + static QString computeDocumentPath(const QString& path); + static bool canCreateFile(const QString& fullPath); }; #endif // hifi_FileUtils_h diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 4e6bcf62b9..7848cb6372 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -694,7 +694,9 @@ void OffscreenQmlSurface::create() { // Setup the update of the QML media components with the current audio output device QObject::connect(&_audioOutputUpdateTimer, &QTimer::timeout, this, [this]() { - new AudioHandler(sharedFromThis(), _currentAudioOutputDevice); + if (_currentAudioOutputDevice.size() > 0) { + new AudioHandler(sharedFromThis(), _currentAudioOutputDevice); + } }); int waitForAudioQmlMs = 200; _audioOutputUpdateTimer.setInterval(waitForAudioQmlMs); @@ -710,6 +712,7 @@ void OffscreenQmlSurface::create() { } void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate) { + _currentAudioOutputDevice = deviceName; if (_rootItem != nullptr && !isHtmlUpdate) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); } @@ -717,18 +720,16 @@ void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, boo } void OffscreenQmlSurface::forceHtmlAudioOutputDeviceUpdate() { - auto audioIO = DependencyManager::get(); - QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName(); - QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, - Q_ARG(QString, deviceName), Q_ARG(bool, true)); + if (_currentAudioOutputDevice.size() > 0) { + QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, + Q_ARG(QString, _currentAudioOutputDevice), Q_ARG(bool, true)); + } } void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() { if (QThread::currentThread() != qApp->thread()) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); } else { - auto audioIO = DependencyManager::get(); - _currentAudioOutputDevice = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName(); if (_audioOutputUpdateTimer.isActive()) { _audioOutputUpdateTimer.stop(); } diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 0a0e67756d..d8658f1c14 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -20,7 +20,7 @@ namespace { bool isAuthableHighFidelityURL(const QUrl& url) { - auto metaverseServerURL = NetworkingConstants::METAVERSE_SERVER_URL; + auto metaverseServerURL = NetworkingConstants::METAVERSE_SERVER_URL(); static const QStringList HF_HOSTS = { "highfidelity.com", "highfidelity.io", metaverseServerURL.toString(), "metaverse.highfidelity.io" diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 915b3b3680..51f927f224 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -412,6 +412,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); triggers: [{action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"}], posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand, true), hover: true, + scaleWithAvatar: true, distanceScaleEnd: true, hand: LEFT_HAND }); @@ -421,6 +422,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); triggers: [{action: Controller.Standard.RTClick, button: "Focus"}, {action: Controller.Standard.RTClick, button: "Primary"}], posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand, true), hover: true, + scaleWithAvatar: true, distanceScaleEnd: true, hand: RIGHT_HAND }); @@ -431,6 +433,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand, true), triggers: [{action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"}], hover: true, + scaleWithAvatar: true, distanceScaleEnd: true, hand: LEFT_HAND }); @@ -441,6 +444,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand, true), triggers: [{action: Controller.Standard.RTClick, button: "Focus"}, {action: Controller.Standard.RTClick, button: "Primary"}], hover: true, + scaleWithAvatar: true, distanceScaleEnd: true, hand: RIGHT_HAND }); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 7733ca1d28..a51cea67f8 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -269,6 +269,7 @@ function Grabber() { joint: "Mouse", filter: Picks.PICK_ENTITIES, faceAvatar: true, + scaleWithAvatar: true, enabled: true, renderStates: renderStates }); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 878c3b51f1..b361f1120b 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -265,8 +265,10 @@ }); $('.grid-item').find('#price-or-edit').find('a').each(function() { - $(this).attr('data-href', $(this).attr('href')); - $(this).attr('href', '#'); + if ($(this).attr('href') !== '#') { // Guard necessary because of the AJAX nature of Marketplace site + $(this).attr('data-href', $(this).attr('href')); + $(this).attr('href', '#'); + } cost = $(this).closest('.col-xs-3').find('.item-cost').text(); $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 392ff3c0b5..d9003ffeaa 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -892,12 +892,13 @@ } function keyPressEvent(event) { - if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) { + if ((event.text.toUpperCase() === "X") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl + && !event.isAlt) { updateTriggers(1.0, true, Controller.Standard.RightHand); } } function keyReleaseEvent(event) { - if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) { + if (event.text.toUpperCase() === "X" && !event.isAutoRepeat) { updateTriggers(0.0, true, Controller.Standard.RightHand); } } diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index e00560158f..88884a4fee 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -106,7 +106,7 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : auto accountManager = DependencyManager::get(); accountManager->setIsAgent(true); - accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); auto nodeList = DependencyManager::get(); diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp index c5edf27b67..9fd1bf8d4f 100644 --- a/tools/atp-client/src/ATPClientApp.cpp +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -145,7 +145,7 @@ ATPClientApp::ATPClientApp(int argc, char* argv[]) : auto accountManager = DependencyManager::get(); accountManager->setIsAgent(true); - accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); auto nodeList = DependencyManager::get(); diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 321f81ba8f..00344179bd 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -8,13 +8,14 @@ setup_memory_debugger() if (WIN32) package_libraries_for_deployment() -endif () - -if (UNIX) +elseif (UNIX AND NOT APPLE) find_package(Threads REQUIRED) if(THREADS_HAVE_PTHREAD_ARG) target_compile_options(PUBLIC oven "-pthread") endif() -endif () +elseif (APPLE) + # Fix up the rpath so macdeployqt works + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +endif() install_beside_console()