diff --git a/examples/libraries/walkApi.js b/examples/libraries/walkApi.js index 8b99ad2a98..d1192deee7 100644 --- a/examples/libraries/walkApi.js +++ b/examples/libraries/walkApi.js @@ -82,7 +82,7 @@ Avatar = function() { // only need to zero right leg IK chain and hips if (IKChain === "RightLeg" || joint === "Hips" ) { - MyAvatar.setJointData(joint, Quat.fromPitchYawRollDegrees(0, 0, 0)); + MyAvatar.setJointRotation(joint, Quat.fromPitchYawRollDegrees(0, 0, 0)); } } this.calibration.hipsToFeet = MyAvatar.getJointPosition("Hips").y - MyAvatar.getJointPosition("RightToeBase").y; @@ -112,16 +112,16 @@ Avatar = function() { this.poseFingers = function() { for (knuckle in walkAssets.animationReference.leftHand) { if (walkAssets.animationReference.leftHand[knuckle].IKChain === "LeftHandThumb") { - MyAvatar.setJointData(knuckle, Quat.fromPitchYawRollDegrees(0, 0, -4)); + MyAvatar.setJointRotation(knuckle, Quat.fromPitchYawRollDegrees(0, 0, -4)); } else { - MyAvatar.setJointData(knuckle, Quat.fromPitchYawRollDegrees(16, 0, 5)); + MyAvatar.setJointRotation(knuckle, Quat.fromPitchYawRollDegrees(16, 0, 5)); } } for (knuckle in walkAssets.animationReference.rightHand) { if (walkAssets.animationReference.rightHand[knuckle].IKChain === "RightHandThumb") { - MyAvatar.setJointData(knuckle, Quat.fromPitchYawRollDegrees(0, 0, 4)); + MyAvatar.setJointRotation(knuckle, Quat.fromPitchYawRollDegrees(0, 0, 4)); } else { - MyAvatar.setJointData(knuckle, Quat.fromPitchYawRollDegrees(16, 0, -5)); + MyAvatar.setJointRotation(knuckle, Quat.fromPitchYawRollDegrees(16, 0, -5)); } } }; diff --git a/examples/walk.js b/examples/walk.js index 8d54fecc92..0b5bcab65a 100644 --- a/examples/walk.js +++ b/examples/walk.js @@ -449,6 +449,6 @@ function renderMotion() { } // apply rotations - MyAvatar.setJointData(jointName, Quat.fromVec3Degrees(jointRotations)); + MyAvatar.setJointRotation(jointName, Quat.fromVec3Degrees(jointRotations)); } } \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e0e5003830..c9a07c1d13 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1109,13 +1109,10 @@ void Application::paintGL() { } } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { if (isHMDMode()) { - glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix()); - _myCamera.setRotation(myAvatar->getWorldAlignedOrientation() * hmdRotation); - // Ignore MenuOption::CenterPlayerInView in HMD view - glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix()); - _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + myAvatar->getOrientation() - * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f) + hmdOffset)); + auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); + _myCamera.setRotation(glm::normalize(glm::quat_cast(hmdWorldMat))); + auto worldBoomOffset = myAvatar->getOrientation() * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)); + _myCamera.setPosition(extractTranslation(hmdWorldMat) + worldBoomOffset); } else { _myCamera.setRotation(myAvatar->getHead()->getOrientation()); if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3d671b0447..008694717f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -24,6 +24,7 @@ #include "Application.h" #include "AccountManager.h" +#include "assets/ATPAssetMigrator.h" #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" #include "devices/DdeFaceTracker.h" @@ -354,7 +355,7 @@ Menu::Menu() { MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets"); auto& assetDialogFactory = AssetUploadDialogFactory::getInstance(); - assetDialogFactory.setParent(this); + assetDialogFactory.setDialogParent(this); QAction* assetUpload = addActionToQMenuAndActionHash(assetDeveloperMenu, MenuOption::UploadAsset, @@ -365,6 +366,13 @@ Menu::Menu() { // disable the asset upload action by default - it gets enabled only if asset server becomes present assetUpload->setEnabled(false); + auto& atpMigrator = ATPAssetMigrator::getInstance(); + atpMigrator.setDialogParent(this); + + QAction* assetMigration = addActionToQMenuAndActionHash(assetDeveloperMenu, MenuOption::AssetMigration, + 0, &atpMigrator, + SLOT(loadEntityServerFile())); + MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 216a410603..162fad1b9f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -135,6 +135,7 @@ namespace MenuOption { const QString AnimDebugDrawAnimPose = "Debug Draw Animation"; const QString AnimDebugDrawBindPose = "Debug Draw Bind Pose"; const QString Antialiasing = "Antialiasing"; + const QString AssetMigration = "ATP Asset Migration"; const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; const QString AudioNoiseReduction = "Audio Noise Reduction"; diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp new file mode 100644 index 0000000000..fadf4ca7ad --- /dev/null +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -0,0 +1,273 @@ +// +// ATPAssetMigrator.cpp +// interface/src/assets +// +// Created by Stephen Birarda on 2015-10-12. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ATPAssetMigrator.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "../ui/AssetUploadDialogFactory.h" + +Q_DECLARE_LOGGING_CATEGORY(asset_migrator); +Q_LOGGING_CATEGORY(asset_migrator, "hf.asset_migrator"); + +ATPAssetMigrator& ATPAssetMigrator::getInstance() { + static ATPAssetMigrator instance; + return instance; +} + +static const QString ENTITIES_OBJECT_KEY = "Entities"; +static const QString MODEL_URL_KEY = "modelURL"; +static const QString MESSAGE_BOX_TITLE = "ATP Asset Migration"; + +void ATPAssetMigrator::loadEntityServerFile() { + auto filename = QFileDialog::getOpenFileName(_dialogParent, "Select an entity-server content file to migrate", + QString(), QString("Entity-Server Content (*.gz)")); + + if (!filename.isEmpty()) { + qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename; + + static const QString MIGRATION_CONFIRMATION_TEXT { + "The ATP Asset Migration process will scan the selected entity-server file, upload discovered resources to the"\ + " current asset-server and then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\ + " continue?\n\nMake sure you are connected to the right domain." + }; + + auto button = QMessageBox::question(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT, + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (button == QMessageBox::No) { + return; + } + + // try to open the file at the given filename + QFile modelsFile { filename }; + + if (modelsFile.open(QIODevice::ReadOnly)) { + QByteArray compressedJsonData = modelsFile.readAll(); + QByteArray jsonData; + + if (!gunzip(compressedJsonData, jsonData)) { + QMessageBox::warning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format."); + } + + QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData); + _entitiesArray = modelsJSON.object()["Entities"].toArray(); + + for (auto jsonValue : _entitiesArray) { + QJsonObject entityObject = jsonValue.toObject(); + QString modelURLString = entityObject.value(MODEL_URL_KEY).toString(); + + if (!modelURLString.isEmpty()) { + QUrl modelURL = QUrl(modelURLString); + + if (!_ignoredUrls.contains(modelURL) + && (modelURL.scheme() == URL_SCHEME_HTTP || modelURL.scheme() == URL_SCHEME_HTTPS + || modelURL.scheme() == URL_SCHEME_FILE || modelURL.scheme() == URL_SCHEME_FTP)) { + + if (_pendingReplacements.contains(modelURL)) { + // we already have a request out for this asset, just store the QJsonValueRef + // so we can do the hash replacement when the request comes back + _pendingReplacements.insert(modelURL, jsonValue); + } else if (_uploadedAssets.contains(modelURL)) { + // we already have a hash for this asset + // so just do the replacement immediately + entityObject[MODEL_URL_KEY] = _uploadedAssets.value(modelURL).toString(); + jsonValue = entityObject; + } else if (wantsToMigrateResource(modelURL)) { + auto request = ResourceManager::createResourceRequest(this, modelURL); + + if (request) { + qCDebug(asset_migrator) << "Requesting" << modelURL << "for ATP asset migration"; + + // add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL + // to an ATP one once ready + _pendingReplacements.insert(modelURL, jsonValue); + + connect(request, &ResourceRequest::finished, this, [=]() { + if (request->getResult() == ResourceRequest::Success) { + migrateResource(request); + } else { + QMessageBox::warning(_dialogParent, "Error", + QString("Could not retrieve asset at %1").arg(modelURL.toString())); + } + request->deleteLater(); + }); + + request->send(); + } else { + QMessageBox::warning(_dialogParent, "Error", + QString("Could not create request for asset at %1").arg(modelURL.toString())); + } + + } else { + _ignoredUrls.insert(modelURL); + } + } + } + } + + _doneReading = true; + + } else { + QMessageBox::warning(_dialogParent, "Error", + "There was a problem loading that entity-server file for ATP asset migration. Please try again"); + } + } +} + +void ATPAssetMigrator::migrateResource(ResourceRequest* request) { + // use an asset client to upload the asset + auto assetClient = DependencyManager::get(); + + QFileInfo assetInfo { request->getUrl().fileName() }; + + auto upload = assetClient->createUpload(request->getData(), assetInfo.completeSuffix()); + + if (upload) { + // add this URL to our hash of AssetUpload to original URL + _originalURLs.insert(upload, request->getUrl()); + + qCDebug(asset_migrator) << "Starting upload of asset from" << request->getUrl(); + + // connect to the finished signal so we know when the AssetUpload is done + QObject::connect(upload, &AssetUpload::finished, this, &ATPAssetMigrator::assetUploadFinished); + + // start the upload now + upload->start(); + } else { + // show a QMessageBox to say that there is no local asset server + QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \ + " to a local asset-server.").arg(assetInfo.fileName()); + + QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText); + } +} + +void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& hash) { + if (upload->getError() == AssetUpload::NoError) { + + const auto& modelURL = _originalURLs[upload]; + + // successfully uploaded asset - make any required replacements found in the pending replacements + auto values = _pendingReplacements.values(modelURL); + + + QString atpURL = getATPUrl(hash, upload->getExtension()).toString(); + + for (auto value : values) { + // replace the modelURL in this QJsonValueRef with the hash + QJsonObject valueObject = value.toObject(); + valueObject[MODEL_URL_KEY] = atpURL; + value = valueObject; + } + + // add this URL to our list of uploaded assets + _uploadedAssets.insert(modelURL, atpURL); + + // pull the replaced models from _pendingReplacements + _pendingReplacements.remove(modelURL); + + // are we out of pending replacements? if so it is time to save the entity-server file + if (_doneReading && _pendingReplacements.empty()) { + saveEntityServerFile(); + + // reset after the attempted save, success or fail + reset(); + } + } else { + AssetUploadDialogFactory::showErrorDialog(upload, _dialogParent); + } + + upload->deleteLater(); +} + +bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) { + static bool hasAskedForCompleteMigration { false }; + static bool wantsCompleteMigration { false }; + + if (!hasAskedForCompleteMigration) { + // this is the first resource migration - ask the user if they just want to migrate everything + static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n\n"\ + "Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n\n"\ + "Select \"No\" to be prompted for each discovered asset." + }; + + auto button = QMessageBox::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT, + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (button == QMessageBox::Yes) { + wantsCompleteMigration = true; + } + + hasAskedForCompleteMigration = true; + } + + if (wantsCompleteMigration) { + return true; + } else { + // present a dialog asking the user if they want to migrate this specific resource + auto button = QMessageBox::question(_dialogParent, MESSAGE_BOX_TITLE, + "Would you like to migrate the following resource?\n" + url.toString(), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + return button == QMessageBox::Yes; + } +} + +void ATPAssetMigrator::saveEntityServerFile() { + // show a dialog to ask the user where they want to save the file + QString saveName = QFileDialog::getSaveFileName(_dialogParent, "Save Migrated Entities File"); + + QFile saveFile { saveName }; + + if (saveFile.open(QIODevice::WriteOnly)) { + QJsonObject rootObject; + rootObject[ENTITIES_OBJECT_KEY] = _entitiesArray; + + QJsonDocument newDocument { rootObject }; + QByteArray jsonDataForFile; + + if (gzip(newDocument.toJson(), jsonDataForFile, -1)) { + + saveFile.write(jsonDataForFile); + saveFile.close(); + + QMessageBox::information(_dialogParent, "Success", + QString("Your new entities file has been saved at %1").arg(saveName)); + } else { + QMessageBox::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file."); + } + + } else { + QMessageBox::warning(_dialogParent, "Error", + QString("Could not open file at %1 to write new entities file to.").arg(saveName)); + } +} + +void ATPAssetMigrator::reset() { + _entitiesArray = QJsonArray(); + _doneReading = false; + _pendingReplacements.clear(); + _uploadedAssets.clear(); + _originalURLs.clear(); + _ignoredUrls.clear(); +} diff --git a/interface/src/assets/ATPAssetMigrator.h b/interface/src/assets/ATPAssetMigrator.h new file mode 100644 index 0000000000..454eb1eac1 --- /dev/null +++ b/interface/src/assets/ATPAssetMigrator.h @@ -0,0 +1,55 @@ +// +// ATPAssetMigrator.h +// interface/src/assets +// +// Created by Stephen Birarda on 2015-10-12. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_ATPAssetMigrator_h +#define hifi_ATPAssetMigrator_h + +#include +#include +#include +#include + +class AssetUpload; +class ResourceRequest; + +class ATPAssetMigrator : public QObject { + Q_OBJECT +public: + static ATPAssetMigrator& getInstance(); + + void setDialogParent(QWidget* dialogParent) { _dialogParent = dialogParent; } +public slots: + void loadEntityServerFile(); +private slots: + void assetUploadFinished(AssetUpload* upload, const QString& hash); +private: + void migrateResource(ResourceRequest* request); + void saveEntityServerFile(); + + void reset(); + + bool wantsToMigrateResource(const QUrl& url); + + QWidget* _dialogParent = nullptr; + QJsonArray _entitiesArray; + + bool _doneReading { false }; + + QMultiHash _pendingReplacements; + QHash _uploadedAssets; + QHash _originalURLs; + QSet _ignoredUrls; +}; + + +#endif // hifi_ATPAssetMigrator_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d6630b8ac1..5920543dca 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -326,6 +326,7 @@ static bool capsuleCheck(const glm::vec3& pos, float capsuleLen, float capsuleRa // as it moves through the world. void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { + // calc deltaTime auto now = usecTimestampNow(); auto deltaUsecs = now - _lastUpdateFromHMDTime; _lastUpdateFromHMDTime = now; @@ -340,21 +341,61 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { bool hmdIsAtRest = _hmdAtRestDetector.update(deltaTime, _hmdSensorPosition, _hmdSensorOrientation); - const float STRAIGHTENING_LEAN_DURATION = 0.5f; // seconds + // It can be more accurate/smooth to use velocity rather than position, + // but some modes (e.g., hmd standing) update position without updating velocity. + // So, let's create our own workingVelocity from the worldPosition... + glm::vec3 positionDelta = getPosition() - _lastPosition; + glm::vec3 workingVelocity = positionDelta / deltaTime; + _lastPosition = getPosition(); + const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec + const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec + bool isMoving; + if (_lastIsMoving) { + isMoving = glm::length(workingVelocity) >= MOVE_EXIT_SPEED_THRESHOLD; + } else { + isMoving = glm::length(workingVelocity) > MOVE_ENTER_SPEED_THRESHOLD; + } + + bool justStartedMoving = (_lastIsMoving != isMoving) && isMoving; + _lastIsMoving = isMoving; + + if (shouldBeginStraighteningLean() || hmdIsAtRest || justStartedMoving) { + beginStraighteningLean(); + } + + processStraighteningLean(deltaTime); +} + +void MyAvatar::beginStraighteningLean() { + // begin homing toward derived body position. + if (!_straighteningLean) { + _straighteningLean = true; + _straighteningLeanAlpha = 0.0f; + } +} + +bool MyAvatar::shouldBeginStraighteningLean() const { // define a vertical capsule const float STRAIGHTENING_LEAN_CAPSULE_RADIUS = 0.2f; // meters const float STRAIGHTENING_LEAN_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters. + // detect if the derived body position is outside of a capsule around the _bodySensorMatrix auto newBodySensorMatrix = deriveBodyFromHMDSensor(); glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix); - if (!_straighteningLean && (capsuleCheck(diff, STRAIGHTENING_LEAN_CAPSULE_LENGTH, STRAIGHTENING_LEAN_CAPSULE_RADIUS) || hmdIsAtRest)) { + bool isBodyPosOutsideCapsule = capsuleCheck(diff, STRAIGHTENING_LEAN_CAPSULE_LENGTH, STRAIGHTENING_LEAN_CAPSULE_RADIUS); - // begin homing toward derived body position. - _straighteningLean = true; - _straighteningLeanAlpha = 0.0f; + if (isBodyPosOutsideCapsule) { + return true; + } else { + return false; + } +} - } else if (_straighteningLean) { +void MyAvatar::processStraighteningLean(float deltaTime) { + if (_straighteningLean) { + + const float STRAIGHTENING_LEAN_DURATION = 0.5f; // seconds auto newBodySensorMatrix = deriveBodyFromHMDSensor(); auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; @@ -1096,32 +1137,41 @@ void MyAvatar::setJointTranslations(QVector jointTranslations) { } void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { - if (QThread::currentThread() == thread()) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation), + Q_ARG(const glm::vec3&, translation)); + return; } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); } void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { - if (QThread::currentThread() == thread()) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); + return; } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY); } void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { - if (QThread::currentThread() == thread()) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); + return; } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } void MyAvatar::clearJointData(int index) { - if (QThread::currentThread() == thread()) { - // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority - _rig->setJointState(index, false, glm::quat(), glm::vec3(), 0.0f); - _rig->clearJointAnimationPriority(index); + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); + return; } + // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority + _rig->setJointState(index, false, glm::quat(), glm::vec3(), 0.0f); + _rig->clearJointAnimationPriority(index); } void MyAvatar::clearJointsData() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0e333d2ce5..7347419fee 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -262,6 +262,10 @@ private: const RecorderPointer getRecorder() const { return _recorder; } const PlayerPointer getPlayer() const { return _player; } + void beginStraighteningLean(); + bool shouldBeginStraighteningLean() const; + void processStraighteningLean(float deltaTime); + bool cameraInsideHead() const; // These are made private for MyAvatar so that you will use the "use" methods instead @@ -357,6 +361,8 @@ private: quint64 _lastUpdateFromHMDTime = usecTimestampNow(); AtRestDetector _hmdAtRestDetector; + glm::vec3 _lastPosition; + bool _lastIsMoving = false; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/ui/AssetUploadDialogFactory.cpp b/interface/src/ui/AssetUploadDialogFactory.cpp index e169d88c8c..66f72e5b5c 100644 --- a/interface/src/ui/AssetUploadDialogFactory.cpp +++ b/interface/src/ui/AssetUploadDialogFactory.cpp @@ -30,7 +30,7 @@ AssetUploadDialogFactory& AssetUploadDialogFactory::getInstance() { return staticInstance; } -static const QString PERMISSION_DENIED_ERROR = "You do not have permission to upload content to this asset-server."; + void AssetUploadDialogFactory::showDialog() { auto nodeList = DependencyManager::get(); @@ -60,7 +60,7 @@ void AssetUploadDialogFactory::showDialog() { } } else { // we don't have permission to upload to asset server in this domain - show the permission denied error - showErrorDialog(QString(), PERMISSION_DENIED_ERROR); + showErrorDialog(nullptr, _dialogParent, AssetUpload::PERMISSION_DENIED_ERROR); } } @@ -118,42 +118,33 @@ void AssetUploadDialogFactory::handleUploadFinished(AssetUpload* upload, const Q // show the new dialog hashCopyDialog->show(); } else { - // figure out the right error message for the message box - QString additionalError; - - switch (upload->getError()) { - case AssetUpload::PermissionDenied: - additionalError = PERMISSION_DENIED_ERROR; - break; - case AssetUpload::TooLarge: - additionalError = "The uploaded content was too large and could not be stored in the asset-server."; - break; - case AssetUpload::FileOpenError: - additionalError = "The file could not be opened. Please check your permissions and try again."; - break; - case AssetUpload::NetworkError: - additionalError = "The file could not be opened. Please check your network connectivity."; - break; - default: - // not handled, do not show a message box - return; - } - // display a message box with the error - showErrorDialog(QFileInfo(upload->getFilename()).fileName(), additionalError); + showErrorDialog(upload, _dialogParent); } upload->deleteLater(); } -void AssetUploadDialogFactory::showErrorDialog(const QString& filename, const QString& additionalError) { - QString errorMessage; +void AssetUploadDialogFactory::showErrorDialog(AssetUpload* upload, QWidget* dialogParent, const QString& overrideMessage) { + QString filename; - if (!filename.isEmpty()) { - errorMessage += QString("Failed to upload %1.\n\n").arg(filename); + if (upload) { + filename = QFileInfo { upload->getFilename() }.fileName(); } - errorMessage += additionalError; + QString errorMessage = overrideMessage; - QMessageBox::warning(_dialogParent, "Failed Upload", errorMessage); + if (errorMessage.isEmpty() && upload) { + errorMessage = upload->getErrorString(); + } + + QString dialogMessage; + + if (upload) { + dialogMessage += QString("Failed to upload %1.\n\n").arg(filename); + } + + dialogMessage += errorMessage; + + QMessageBox::warning(dialogParent, "Failed Upload", dialogMessage); } diff --git a/interface/src/ui/AssetUploadDialogFactory.h b/interface/src/ui/AssetUploadDialogFactory.h index a8e8765f20..fe3ecf5dc4 100644 --- a/interface/src/ui/AssetUploadDialogFactory.h +++ b/interface/src/ui/AssetUploadDialogFactory.h @@ -25,6 +25,7 @@ public: AssetUploadDialogFactory& operator=(const AssetUploadDialogFactory& rhs) = delete; static AssetUploadDialogFactory& getInstance(); + static void showErrorDialog(AssetUpload* upload, QWidget* dialogParent, const QString& overrideMessage = QString()); void setDialogParent(QWidget* dialogParent) { _dialogParent = dialogParent; } @@ -35,7 +36,7 @@ public slots: private: AssetUploadDialogFactory() = default; - void showErrorDialog(const QString& filename, const QString& additionalError); + QWidget* _dialogParent { nullptr }; }; diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 5304cefe46..c0bb66c2a0 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -93,7 +93,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe const float dt = 0.0f; Triggers triggers; _nextPoses = nextStateNode->evaluate(animVars, dt, triggers); -#if WANT_DEBUGa +#if WANT_DEBUG qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget; #endif _currentState = desiredState; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 340c09060a..25f28a3f64 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -437,16 +437,6 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos static float t = 0.0f; _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); - // default anim vars to notMoving and notTurning - _animVars.set("isMovingForward", false); - _animVars.set("isMovingBackward", false); - _animVars.set("isMovingLeft", false); - _animVars.set("isMovingRight", false); - _animVars.set("isNotMoving", true); - _animVars.set("isTurningLeft", false); - _animVars.set("isTurningRight", false); - _animVars.set("isNotTurning", true); - const float ANIM_WALK_SPEED = 1.4f; // m/s _animVars.set("walkTimeScale", glm::clamp(0.5f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED)); @@ -470,47 +460,102 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } if (glm::length(localVel) > moveThresh) { - if (fabsf(forwardSpeed) > 0.5f * fabsf(lateralSpeed)) { - if (forwardSpeed > 0.0f) { - // forward - _animVars.set("isMovingForward", true); - _animVars.set("isNotMoving", false); - - } else { - // backward - _animVars.set("isMovingBackward", true); - _animVars.set("isNotMoving", false); - } - } else { - if (lateralSpeed > 0.0f) { - // right - _animVars.set("isMovingRight", true); - _animVars.set("isNotMoving", false); - } else { - // left - _animVars.set("isMovingLeft", true); - _animVars.set("isNotMoving", false); - } + if (_desiredState != RigRole::Move) { + _desiredStateAge = 0.0f; } - _state = RigRole::Move; + _desiredState = RigRole::Move; } else { if (fabsf(turningSpeed) > turnThresh) { - if (turningSpeed > 0.0f) { - // turning right - _animVars.set("isTurningRight", true); - _animVars.set("isNotTurning", false); - } else { - // turning left - _animVars.set("isTurningLeft", true); - _animVars.set("isNotTurning", false); + if (_desiredState != RigRole::Turn) { + _desiredStateAge = 0.0f; } - _state = RigRole::Turn; - } else { - // idle - _state = RigRole::Idle; + _desiredState = RigRole::Turn; + } else { // idle + if (_desiredState != RigRole::Idle) { + _desiredStateAge = 0.0f; + } + _desiredState = RigRole::Idle; } } + const float STATE_CHANGE_HYSTERESIS_TIMER = 0.1f; + + if ((_desiredStateAge >= STATE_CHANGE_HYSTERESIS_TIMER) && _desiredState != _state) { + _state = _desiredState; + _desiredStateAge = 0.0f; + } + + _desiredStateAge += deltaTime; + + if (_state == RigRole::Move) { + if (glm::length(localVel) > MOVE_ENTER_SPEED_THRESHOLD) { + if (fabsf(forwardSpeed) > 0.5f * fabsf(lateralSpeed)) { + if (forwardSpeed > 0.0f) { + // forward + _animVars.set("isMovingForward", true); + _animVars.set("isMovingBackward", false); + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isNotMoving", false); + + } else { + // backward + _animVars.set("isMovingBackward", true); + _animVars.set("isMovingForward", false); + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isNotMoving", false); + } + } else { + if (lateralSpeed > 0.0f) { + // right + _animVars.set("isMovingRight", true); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingForward", false); + _animVars.set("isMovingBackward", false); + _animVars.set("isNotMoving", false); + } else { + // left + _animVars.set("isMovingLeft", true); + _animVars.set("isMovingRight", false); + _animVars.set("isMovingForward", false); + _animVars.set("isMovingBackward", false); + _animVars.set("isNotMoving", false); + } + } + _animVars.set("isTurningLeft", false); + _animVars.set("isTurningRight", false); + _animVars.set("isNotTurning", true); + } + } else if (_state == RigRole::Turn) { + if (turningSpeed > 0.0f) { + // turning right + _animVars.set("isTurningRight", true); + _animVars.set("isTurningLeft", false); + _animVars.set("isNotTurning", false); + } else { + // turning left + _animVars.set("isTurningLeft", true); + _animVars.set("isTurningRight", false); + _animVars.set("isNotTurning", false); + } + _animVars.set("isMovingForward", false); + _animVars.set("isMovingBackward", false); + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isNotMoving", true); + } else { + // default anim vars to notMoving and notTurning + _animVars.set("isMovingForward", false); + _animVars.set("isMovingBackward", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRight", false); + _animVars.set("isNotMoving", true); + _animVars.set("isTurningLeft", false); + _animVars.set("isTurningRight", false); + _animVars.set("isNotTurning", true); + } + t += deltaTime; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 6d9f7b4688..71c27e7213 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -236,6 +236,8 @@ public: Move }; RigRole _state = RigRole::Idle; + RigRole _desiredState = RigRole::Idle; + float _desiredStateAge = 0.0f; float _leftHandOverlayAlpha = 0.0f; float _rightHandOverlayAlpha = 0.0f; }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a9ff9541ea..a698c6f374 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1032,13 +1032,30 @@ glm::vec3 AvatarData::getJointTranslation(const QString& name) const { void AvatarData::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointData", Q_ARG(const QString&, name), - Q_ARG(const glm::quat&, rotation)); + QMetaObject::invokeMethod(this, "setJointData", Q_ARG(const QString&, name), Q_ARG(const glm::quat&, rotation), + Q_ARG(const glm::vec3&, translation)); return; } setJointData(getJointIndex(name), rotation, translation); } +void AvatarData::setJointRotation(const QString& name, const glm::quat& rotation) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(const QString&, name), Q_ARG(const glm::quat&, rotation)); + return; + } + setJointRotation(getJointIndex(name), rotation); +} + +void AvatarData::setJointTranslation(const QString& name, const glm::vec3& translation) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(const QString&, name), + Q_ARG(const glm::vec3&, translation)); + return; + } + setJointTranslation(getJointIndex(name), translation); +} + void AvatarData::setJointRotation(int index, const glm::quat& rotation) { if (index == -1) { return; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e4022fd474..3abd63bf63 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -251,6 +251,8 @@ public: Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const; Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation); + Q_INVOKABLE void setJointRotation(const QString& name, const glm::quat& rotation); + Q_INVOKABLE void setJointTranslation(const QString& name, const glm::vec3& translation); Q_INVOKABLE void clearJointData(const QString& name); Q_INVOKABLE bool isJointDataValid(const QString& name) const; Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index 58675eab4d..3e2290f104 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -11,6 +11,8 @@ #include "OculusHelpers.h" +#include + #if (OVR_MAJOR_VERSION >= 6) // A base class for FBO wrappers that need to use the Oculus C @@ -135,6 +137,19 @@ const QString & OculusDisplayPlugin::getName() const { return NAME; } +static const QString MONO_PREVIEW = "Mono Preview"; +static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; + +void OculusDisplayPlugin::activate() { + + CONTAINER->addMenuItem(MENU_PATH(), MONO_PREVIEW, + [this](bool clicked) { + _monoPreview = clicked; + }, true, true); + CONTAINER->removeMenu(FRAMERATE); + OculusBaseDisplayPlugin::activate(); +} + void OculusDisplayPlugin::customizeContext() { WindowOpenGLDisplayPlugin::customizeContext(); #if (OVR_MAJOR_VERSION >= 6) @@ -149,7 +164,7 @@ void OculusDisplayPlugin::customizeContext() { #endif enableVsync(false); // Only enable mirroring if we know vsync is disabled - _enableMirror = !isVsyncEnabled(); + _enablePreview = !isVsyncEnabled(); } void OculusDisplayPlugin::deactivate() { @@ -169,10 +184,15 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi // controlling vsync wglSwapIntervalEXT(0); - // screen mirroring - if (_enableMirror) { + // screen preview mirroring + if (_enablePreview) { auto windowSize = toGlm(_window->size()); - Context::Viewport(windowSize.x, windowSize.y); + if (_monoPreview) { + Context::Viewport(windowSize.x * 2, windowSize.y); + Context::Scissor(0, windowSize.y, windowSize.x, windowSize.y); + } else { + Context::Viewport(windowSize.x, windowSize.y); + } glBindTexture(GL_TEXTURE_2D, finalTexture); GLenum err = glGetError(); Q_ASSERT(0 == err); @@ -216,7 +236,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi otherwise the swapbuffer delay will interefere with the framerate of the headset */ void OculusDisplayPlugin::finishFrame() { - if (_enableMirror) { + if (_enablePreview) { swapBuffers(); } doneCurrent(); diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h index 7db83884cd..c1224ecf3a 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h @@ -14,6 +14,7 @@ using SwapFboPtr = QSharedPointer; class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: + virtual void activate() override; virtual void deactivate() override; virtual const QString & getName() const override; @@ -25,7 +26,8 @@ protected: private: static const QString NAME; - bool _enableMirror{ false }; + bool _enablePreview { false }; + bool _monoPreview { true }; #if (OVR_MAJOR_VERSION >= 6) SwapFboPtr _sceneFbo; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 2ea79ed2e0..4f7b0a1a78 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -61,6 +61,8 @@ glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProje return eyeProjection; } +static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; + std::vector _screenActions; void StereoDisplayPlugin::activate() { auto screens = qApp->screens(); @@ -76,6 +78,9 @@ void StereoDisplayPlugin::activate() { [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); _screenActions[i] = action; } + + CONTAINER->removeMenu(FRAMERATE); + CONTAINER->setFullscreen(qApp->primaryScreen()); WindowOpenGLDisplayPlugin::activate(); } diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 87af2a5cf8..b7f1205847 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -65,43 +65,65 @@ void AssetClient::init() { } } +bool haveAssetServer() { + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + if (!assetServer) { + qCWarning(asset_client) << "Could not complete AssetClient operation " + << "since you are not currently connected to an asset-server."; + return false; + } + + return true; +} + AssetRequest* AssetClient::createRequest(const QString& hash, const QString& extension) { if (hash.length() != SHA256_HASH_HEX_LENGTH) { qCWarning(asset_client) << "Invalid hash size"; return nullptr; } - auto nodeList = DependencyManager::get(); - SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); - - if (!assetServer) { - qCWarning(asset_client).nospace() << "Could not request " << hash << "." << extension - << " since you are not currently connected to an asset-server."; + if (haveAssetServer()) { + auto request = new AssetRequest(hash, extension); + + // Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case) + request->moveToThread(thread()); + request->setParent(this); + + return request; + } else { return nullptr; } - - auto request = new AssetRequest(hash, extension); - - // Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case) - request->moveToThread(thread()); - - return request; } + + AssetUpload* AssetClient::createUpload(const QString& filename) { - auto nodeList = DependencyManager::get(); - SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); - if (!assetServer) { - qCWarning(asset_client) << "Could not upload" << filename << "since you are not currently connected to an asset-server."; + if (haveAssetServer()) { + auto upload = new AssetUpload(filename); + + upload->moveToThread(thread()); + upload->setParent(this); + + return upload; + } else { return nullptr; } - - auto upload = new AssetUpload(this, filename); +} - upload->moveToThread(thread()); - - return upload; +AssetUpload* AssetClient::createUpload(const QByteArray& data, const QString& extension) { + if (haveAssetServer()) { + auto upload = new AssetUpload(data, extension); + + upload->moveToThread(thread()); + upload->setParent(this); + + return upload; + } else { + return nullptr; + } } bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end, diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 9b82e63b58..22933ea30b 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -45,6 +45,7 @@ public: Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension); Q_INVOKABLE AssetUpload* createUpload(const QString& filename); + Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension); private slots: void handleAssetGetInfoReply(QSharedPointer packet, SharedNodePointer senderNode); diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 121b4cd4fd..7e647e9142 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -127,8 +127,3 @@ void AssetRequest::start() { }); }); } - -QUrl AssetRequest::getUrl() const { - return ::getUrl(_hash, _extension); -} - diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h index a5275e718a..3c3459b15d 100644 --- a/libraries/networking/src/AssetRequest.h +++ b/libraries/networking/src/AssetRequest.h @@ -46,7 +46,7 @@ public: const QByteArray& getData() const { return _data; } const State& getState() const { return _state; } const Error& getError() const { return _error; } - QUrl getUrl() const; + QUrl getUrl() const { return ::getATPUrl(_hash, _extension); } signals: void finished(AssetRequest* thisRequest); diff --git a/libraries/networking/src/AssetUpload.cpp b/libraries/networking/src/AssetUpload.cpp index 6f4fcbf1f2..e6f467e717 100644 --- a/libraries/networking/src/AssetUpload.cpp +++ b/libraries/networking/src/AssetUpload.cpp @@ -17,65 +17,94 @@ #include "AssetClient.h" #include "NetworkLogging.h" -AssetUpload::AssetUpload(QObject* object, const QString& filename) : +const QString AssetUpload::PERMISSION_DENIED_ERROR = "You do not have permission to upload content to this asset-server."; + +AssetUpload::AssetUpload(const QByteArray& data, const QString& extension) : + _data(data), + _extension(extension) +{ + +} + +AssetUpload::AssetUpload(const QString& filename) : _filename(filename) { } +QString AssetUpload::getErrorString() const { + // figure out the right error message for error + switch (_error) { + case AssetUpload::PermissionDenied: + return PERMISSION_DENIED_ERROR; + case AssetUpload::TooLarge: + return "The uploaded content was too large and could not be stored in the asset-server."; + case AssetUpload::FileOpenError: + return "The file could not be opened. Please check your permissions and try again."; + case AssetUpload::NetworkError: + return "The file could not be opened. Please check your network connectivity."; + default: + // not handled, do not show a message box + return QString(); + } +} + void AssetUpload::start() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "start"); return; } - // try to open the file at the given filename - QFile file { _filename }; - - if (file.open(QIODevice::ReadOnly)) { + if (_data.isEmpty() && !_filename.isEmpty()) { + // try to open the file at the given filename + QFile file { _filename }; - // file opened, read the data and grab the extension - _extension = QFileInfo(_filename).suffix(); - - _data = file.readAll(); - - // ask the AssetClient to upload the asset and emit the proper signals from the passed callback - auto assetClient = DependencyManager::get(); - - qCDebug(asset_client) << "Attempting to upload" << _filename << "to asset-server."; - - assetClient->uploadAsset(_data, _extension, [this](bool responseReceived, AssetServerError error, - const QString& hash){ - if (!responseReceived) { - _error = NetworkError; - } else { - switch (error) { - case AssetServerError::NoError: - _error = NoError; - break; - case AssetServerError::AssetTooLarge: - _error = TooLarge; - break; - case AssetServerError::PermissionDenied: - _error = PermissionDenied; - break; - default: - _error = FileOpenError; - break; - } - } + if (file.open(QIODevice::ReadOnly)) { - if (_error == NoError && hash == hashData(_data).toHex()) { - saveToCache(getUrl(hash, _extension), _data); - } + // file opened, read the data and grab the extension + _extension = QFileInfo(_filename).suffix(); - emit finished(this, hash); - }); - } else { - // we couldn't open the file - set the error result - _error = FileOpenError; - - // emit that we are done - emit finished(this, QString()); + _data = file.readAll(); + } else { + // we couldn't open the file - set the error result + _error = FileOpenError; + + // emit that we are done + emit finished(this, QString()); + } } + + // ask the AssetClient to upload the asset and emit the proper signals from the passed callback + auto assetClient = DependencyManager::get(); + + if (!_filename.isEmpty()) { + qCDebug(asset_client) << "Attempting to upload" << _filename << "to asset-server."; + } + + assetClient->uploadAsset(_data, _extension, [this](bool responseReceived, AssetServerError error, const QString& hash){ + if (!responseReceived) { + _error = NetworkError; + } else { + switch (error) { + case AssetServerError::NoError: + _error = NoError; + break; + case AssetServerError::AssetTooLarge: + _error = TooLarge; + break; + case AssetServerError::PermissionDenied: + _error = PermissionDenied; + break; + default: + _error = FileOpenError; + break; + } + } + + if (_error == NoError && hash == hashData(_data).toHex()) { + saveToCache(getATPUrl(hash, _extension), _data); + } + + emit finished(this, hash); + }); } diff --git a/libraries/networking/src/AssetUpload.h b/libraries/networking/src/AssetUpload.h index 4047a44923..92f677cbf2 100644 --- a/libraries/networking/src/AssetUpload.h +++ b/libraries/networking/src/AssetUpload.h @@ -35,13 +35,17 @@ public: FileOpenError }; - AssetUpload(QObject* parent, const QString& filename); + static const QString PERMISSION_DENIED_ERROR; + + AssetUpload(const QString& filename); + AssetUpload(const QByteArray& data, const QString& extension); Q_INVOKABLE void start(); const QString& getFilename() const { return _filename; } const QString& getExtension() const { return _extension; } const Error& getError() const { return _error; } + QString getErrorString() const; signals: void finished(AssetUpload* upload, const QString& hash); @@ -49,8 +53,8 @@ signals: private: QString _filename; - QString _extension; QByteArray _data; + QString _extension; Error _error; }; diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index 7311d73525..f37e0af820 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -19,7 +19,7 @@ #include "ResourceManager.h" -QUrl getUrl(const QString& hash, const QString& extension) { +QUrl getATPUrl(const QString& hash, const QString& extension) { if (!extension.isEmpty()) { return QUrl(QString("%1:%2.%3").arg(URL_SCHEME_ATP, hash, extension)); } else { @@ -66,4 +66,4 @@ bool saveToCache(const QUrl& url, const QByteArray& file) { qCWarning(asset_client) << "No disk cache to save assets to."; } return false; -} \ No newline at end of file +} diff --git a/libraries/networking/src/AssetUtils.h b/libraries/networking/src/AssetUtils.h index 5fd5c9144d..21b6b3f434 100644 --- a/libraries/networking/src/AssetUtils.h +++ b/libraries/networking/src/AssetUtils.h @@ -32,7 +32,7 @@ enum AssetServerError : uint8_t { PermissionDenied }; -QUrl getUrl(const QString& hash, const QString& extension = QString()); +QUrl getATPUrl(const QString& hash, const QString& extension = QString()); QByteArray hashData(const QByteArray& data);