diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 0421195612..e657587a7a 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -13,9 +13,25 @@ setup_memory_debugger() link_hifi_libraries( audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver - controllers physics plugins midi baking image + controllers physics plugins midi image ) +add_dependencies(${TARGET_NAME} oven) + +if (WIN32) + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + $ + $) +else() + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink + $ + $/oven) +endif() + if (WIN32) package_libraries_for_deployment() endif() diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index ca0f222e0c..1b533f10f3 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -29,11 +29,10 @@ #include #include -#include -#include #include #include #include +#include #include "AssetServerLogging.h" #include "BakeAssetTask.h" @@ -250,7 +249,7 @@ AssetServer::AssetServer(ReceivedMessage& message) : image::setNormalTexturesCompressionEnabled(true); image::setCubeTexturesCompressionEnabled(true); - BAKEABLE_TEXTURE_EXTENSIONS = TextureBaker::getSupportedFormats(); + BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats(); qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS; // Most of the work will be I/O bound, reading from disk and constructing packet objects, @@ -416,6 +415,9 @@ void AssetServer::completeSetup() { if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) { _filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS; } + + PathUtils::removeTemporaryApplicationDirs(); + PathUtils::removeTemporaryApplicationDirs("Oven"); } void AssetServer::cleanupUnmappedFiles() { diff --git a/assignment-client/src/assets/BakeAssetTask.cpp b/assignment-client/src/assets/BakeAssetTask.cpp index 6c78d2baf3..49322ca4cb 100644 --- a/assignment-client/src/assets/BakeAssetTask.cpp +++ b/assignment-client/src/assets/BakeAssetTask.cpp @@ -11,11 +11,18 @@ #include "BakeAssetTask.h" -#include +#include + +#include +#include -#include #include -#include + +static const int OVEN_STATUS_CODE_SUCCESS { 0 }; +static const int OVEN_STATUS_CODE_FAIL { 1 }; +static const int OVEN_STATUS_CODE_ABORT { 2 }; + +std::once_flag registerMetaTypesFlag; BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) : _assetHash(assetHash), @@ -23,6 +30,10 @@ BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetP _filePath(filePath) { + std::call_once(registerMetaTypesFlag, []() { + qRegisterMetaType("QProcess::ProcessError"); + qRegisterMetaType("QProcess::ExitStatus"); + }); } void cleanupTempFiles(QString tempOutputDir, std::vector files) { @@ -41,67 +52,76 @@ void cleanupTempFiles(QString tempOutputDir, std::vector files) { }; void BakeAssetTask::run() { - _isBaking.store(true); - - qRegisterMetaType >("QVector"); - TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); }; - - QString tempOutputDir; - - if (_assetPath.endsWith(".fbx")) { - tempOutputDir = PathUtils::generateTemporaryDir(); - _baker = std::unique_ptr { - new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir) - }; - } else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) { - _baker = std::unique_ptr{ - new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir()) - }; - } else { - tempOutputDir = PathUtils::generateTemporaryDir(); - _baker = std::unique_ptr { - new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE, - tempOutputDir) - }; + if (_isBaking.exchange(true)) { + qWarning() << "Tried to start bake asset task while already baking"; + return; } - QEventLoop loop; - connect(_baker.get(), &Baker::finished, &loop, &QEventLoop::quit); - connect(_baker.get(), &Baker::aborted, &loop, &QEventLoop::quit); - QMetaObject::invokeMethod(_baker.get(), "bake", Qt::QueuedConnection); - loop.exec(); + QString tempOutputDir = PathUtils::generateTemporaryDir(); + auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir(); + QString path = base.absolutePath() + "/oven"; + QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1); + QStringList args { + "-i", _filePath, + "-o", tempOutputDir, + "-t", extension, + }; - if (_baker->wasAborted()) { - qDebug() << "Aborted baking: " << _assetHash << _assetPath; + _ovenProcess.reset(new QProcess()); - _wasAborted.store(true); + connect(_ovenProcess.get(), static_cast(&QProcess::finished), + this, [this, tempOutputDir](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "Baking process finished: " << exitCode << exitStatus; - cleanupTempFiles(tempOutputDir, _baker->getOutputFiles()); + if (exitStatus == QProcess::CrashExit) { + if (_wasAborted) { + emit bakeAborted(_assetHash, _assetPath); + } else { + QString errors = "Fatal error occurred while baking"; + emit bakeFailed(_assetHash, _assetPath, errors); + } + } else if (exitCode == OVEN_STATUS_CODE_SUCCESS) { + QDir outputDir = tempOutputDir; + auto files = outputDir.entryInfoList(QDir::Files); + QVector outputFiles; + for (auto& file : files) { + outputFiles.push_back(file.absoluteFilePath()); + } - emit bakeAborted(_assetHash, _assetPath); - } else if (_baker->hasErrors()) { - qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors(); + emit bakeComplete(_assetHash, _assetPath, tempOutputDir, outputFiles); + } else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) { + _wasAborted.store(true); + emit bakeAborted(_assetHash, _assetPath); + } else { + QString errors; + if (exitCode == OVEN_STATUS_CODE_FAIL) { + QDir outputDir = tempOutputDir; + auto errorFilePath = outputDir.absoluteFilePath("errors.txt"); + QFile errorFile { errorFilePath }; + if (errorFile.open(QIODevice::ReadOnly)) { + errors = errorFile.readAll(); + errorFile.close(); + } else { + errors = "Unknown error occurred while baking"; + } + } + emit bakeFailed(_assetHash, _assetPath, errors); + } - auto errors = _baker->getErrors().join('\n'); // Join error list into a single string for convenience - - _didFinish.store(true); - - cleanupTempFiles(tempOutputDir, _baker->getOutputFiles()); + }); + qDebug() << "Starting oven for " << _assetPath; + _ovenProcess->start(path, args, QIODevice::ReadOnly); + if (!_ovenProcess->waitForStarted(-1)) { + QString errors = "Oven process failed to start"; emit bakeFailed(_assetHash, _assetPath, errors); - } else { - auto vectorOutputFiles = QVector::fromStdVector(_baker->getOutputFiles()); - - qDebug() << "Finished baking: " << _assetHash << _assetPath << vectorOutputFiles; - - _didFinish.store(true); - - emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles); + return; } + _ovenProcess->waitForFinished(); } void BakeAssetTask::abort() { - if (_baker) { - _baker->abort(); + if (!_wasAborted.exchange(true)) { + _ovenProcess->terminate(); } } diff --git a/assignment-client/src/assets/BakeAssetTask.h b/assignment-client/src/assets/BakeAssetTask.h index 90458ac223..c73a8bff65 100644 --- a/assignment-client/src/assets/BakeAssetTask.h +++ b/assignment-client/src/assets/BakeAssetTask.h @@ -17,9 +17,10 @@ #include #include #include +#include +#include #include -#include class BakeAssetTask : public QObject, public QRunnable { Q_OBJECT @@ -32,7 +33,6 @@ public: void abort(); bool wasAborted() const { return _wasAborted.load(); } - bool didFinish() const { return _didFinish.load(); } signals: void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector outputFiles); @@ -44,9 +44,8 @@ private: AssetHash _assetHash; AssetPath _assetPath; QString _filePath; - std::unique_ptr _baker; + std::unique_ptr _ovenProcess { nullptr }; std::atomic _wasAborted { false }; - std::atomic _didFinish { false }; }; #endif // hifi_BakeAssetTask_h diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index efcf6ccfcf..64f61f0d69 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -26,6 +26,7 @@ Rectangle { // Style color: "#E3E3E3"; // Properties + property bool debug: false; property int myCardWidth: width - upperRightInfoContainer.width; property int myCardHeight: 80; property int rowHeight: 60; @@ -1120,7 +1121,9 @@ Rectangle { break; case 'connections': var data = message.params; - console.log('Got connection data: ', JSON.stringify(data)); + if (pal.debug) { + console.log('Got connection data: ', JSON.stringify(data)); + } connectionsUserModelData = data; sortConnectionsModel(); connectionsLoading.visible = false; diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index c8a63a4d2d..a058f32994 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -905,7 +905,7 @@ Rectangle { } buyTextContainer.color = "#FFC3CD"; buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.error; + buyGlyph.text = hifi.glyphs.alert; buyGlyph.size = 54; } else { if (root.alreadyOwned) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0440d2af3a..0176acf108 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5974,7 +5974,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) { } } - if (defaultUpload) { + if (defaultUpload && !url.fileName().isEmpty() && url.isLocalFile()) { showAssetServerWidget(urlString); } return defaultUpload; diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 1adf5650dd..233e61c8ae 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -18,7 +18,9 @@ GameplayObjects::GameplayObjects() { bool GameplayObjects::addToGameplayObjects(const QUuid& avatarID) { containsData = true; - _avatarIDs.push_back(avatarID); + if (std::find(_avatarIDs.begin(), _avatarIDs.end(), avatarID) == _avatarIDs.end()) { + _avatarIDs.push_back(avatarID); + } return true; } bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) { @@ -28,7 +30,9 @@ bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) { bool GameplayObjects::addToGameplayObjects(const EntityItemID& entityID) { containsData = true; - _entityIDs.push_back(entityID); + if (std::find(_entityIDs.begin(), _entityIDs.end(), entityID) == _entityIDs.end()) { + _entityIDs.push_back(entityID); + } return true; } bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) { @@ -38,7 +42,9 @@ bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) { bool GameplayObjects::addToGameplayObjects(const OverlayID& overlayID) { containsData = true; - _overlayIDs.push_back(overlayID); + if (std::find(_overlayIDs.begin(), _overlayIDs.end(), overlayID) == _overlayIDs.end()) { + _overlayIDs.push_back(overlayID); + } return true; } bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) { @@ -72,28 +78,125 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis } bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) { - _selectedItemsListMap.insert(listName, GameplayObjects()); - emit selectedItemsListChanged(listName); + { + QWriteLocker lock(&_selectionListsLock); + _selectedItemsListMap.insert(listName, GameplayObjects()); + } + onSelectedItemsListChanged(listName); return true; } +QStringList SelectionScriptingInterface::getListNames() const { + QStringList list; + QReadLocker lock(&_selectionListsLock); + list = _selectedItemsListMap.keys(); + return list; +} + +QStringList SelectionScriptingInterface::getHighlightedListNames() const { + QStringList list; + QReadLocker lock(&_highlightStylesLock); + list = _highlightStyleMap.keys(); + return list; +} + +bool SelectionScriptingInterface::enableListHighlight(const QString& listName, const QVariantMap& highlightStyleValues) { + QWriteLocker lock(&_highlightStylesLock); + + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle == _highlightStyleMap.end()) { + highlightStyle = _highlightStyleMap.insert(listName, SelectionHighlightStyle()); + + } + + if (!(*highlightStyle).isBoundToList()) { + setupHandler(listName); + (*highlightStyle).setBoundToList(true); + } + + (*highlightStyle).fromVariantMap(highlightStyleValues); + + auto mainScene = qApp->getMain3DScene(); + if (mainScene) { + render::Transaction transaction; + transaction.resetSelectionHighlight(listName.toStdString(), (*highlightStyle).getStyle()); + mainScene->enqueueTransaction(transaction); + } + else { + qWarning() << "SelectionToSceneHandler::highlightStyleChanged(), Unexpected null scene, possibly during application shutdown"; + } + + return true; +} + +bool SelectionScriptingInterface::disableListHighlight(const QString& listName) { + QWriteLocker lock(&_highlightStylesLock); + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle != _highlightStyleMap.end()) { + if ((*highlightStyle).isBoundToList()) { + } + + _highlightStyleMap.erase(highlightStyle); + + auto mainScene = qApp->getMain3DScene(); + if (mainScene) { + render::Transaction transaction; + transaction.removeHighlightFromSelection(listName.toStdString()); + mainScene->enqueueTransaction(transaction); + } + else { + qWarning() << "SelectionToSceneHandler::highlightStyleChanged(), Unexpected null scene, possibly during application shutdown"; + } + } + + return true; +} + +QVariantMap SelectionScriptingInterface::getListHighlightStyle(const QString& listName) const { + QReadLocker lock(&_highlightStylesLock); + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle == _highlightStyleMap.end()) { + return QVariantMap(); + } else { + return (*highlightStyle).toVariantMap(); + } +} + +render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QString& listName) const { + QReadLocker lock(&_highlightStylesLock); + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle == _highlightStyleMap.end()) { + return render::HighlightStyle(); + } else { + return (*highlightStyle).getStyle(); + } +} + template bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) { - GameplayObjects currentList = _selectedItemsListMap.value(listName); - currentList.addToGameplayObjects(idToAdd); - _selectedItemsListMap.insert(listName, currentList); - - emit selectedItemsListChanged(listName); + { + QWriteLocker lock(&_selectionListsLock); + GameplayObjects currentList = _selectedItemsListMap.value(listName); + currentList.addToGameplayObjects(idToAdd); + _selectedItemsListMap.insert(listName, currentList); + } + onSelectedItemsListChanged(listName); return true; } template bool SelectionScriptingInterface::removeFromGameplayObjects(const QString& listName, T idToRemove) { - GameplayObjects currentList = _selectedItemsListMap.value(listName); - if (currentList.getContainsData()) { - currentList.removeFromGameplayObjects(idToRemove); - _selectedItemsListMap.insert(listName, currentList); - - emit selectedItemsListChanged(listName); + bool listExist = false; + { + QWriteLocker lock(&_selectionListsLock); + auto currentList = _selectedItemsListMap.find(listName); + if (currentList != _selectedItemsListMap.end()) { + listExist = true; + (*currentList).removeFromGameplayObjects(idToRemove); + } + } + if (listExist) { + onSelectedItemsListChanged(listName); return true; - } else { + } + else { return false; } } @@ -102,50 +205,123 @@ template bool SelectionScriptingInterface::removeFromGameplayObjects(c // GameplayObjects SelectionScriptingInterface::getList(const QString& listName) { + QReadLocker lock(&_selectionListsLock); return _selectedItemsListMap.value(listName); } void SelectionScriptingInterface::printList(const QString& listName) { - GameplayObjects currentList = _selectedItemsListMap.value(listName); - if (currentList.getContainsData()) { + QReadLocker lock(&_selectionListsLock); + auto currentList = _selectedItemsListMap.find(listName); + if (currentList != _selectedItemsListMap.end()) { + if ((*currentList).getContainsData()) { - qDebug() << "Avatar IDs:"; - for (auto i : currentList.getAvatarIDs()) { - qDebug() << i << ';'; - } - qDebug() << ""; + qDebug() << "List named " << listName << ":"; + qDebug() << "Avatar IDs:"; + for (auto i : (*currentList).getAvatarIDs()) { + qDebug() << i << ';'; + } + qDebug() << ""; - qDebug() << "Entity IDs:"; - for (auto j : currentList.getEntityIDs()) { - qDebug() << j << ';'; - } - qDebug() << ""; + qDebug() << "Entity IDs:"; + for (auto j : (*currentList).getEntityIDs()) { + qDebug() << j << ';'; + } + qDebug() << ""; - qDebug() << "Overlay IDs:"; - for (auto k : currentList.getOverlayIDs()) { - qDebug() << k << ';'; + qDebug() << "Overlay IDs:"; + for (auto k : (*currentList).getOverlayIDs()) { + qDebug() << k << ';'; + } + qDebug() << ""; + } + else { + qDebug() << "List named " << listName << " empty"; } - qDebug() << ""; } else { - qDebug() << "List named" << listName << "doesn't exist."; + qDebug() << "List named " << listName << " doesn't exist."; + } +} + +QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& listName) const { + QReadLocker lock(&_selectionListsLock); + QVariantMap list; + auto currentList = _selectedItemsListMap.find(listName); + if (currentList != _selectedItemsListMap.end()) { + QList avatarIDs; + QList entityIDs; + QList overlayIDs; + + if ((*currentList).getContainsData()) { + if (!(*currentList).getAvatarIDs().empty()) { + for (auto j : (*currentList).getAvatarIDs()) { + avatarIDs.push_back((QUuid)j); + } + } + if (!(*currentList).getEntityIDs().empty()) { + for (auto j : (*currentList).getEntityIDs()) { + entityIDs.push_back((QUuid)j ); + } + } + if (!(*currentList).getOverlayIDs().empty()) { + for (auto j : (*currentList).getOverlayIDs()) { + overlayIDs.push_back((QUuid)j); + } + } + } + list["avatars"] = (avatarIDs); + list["entities"] = (entityIDs); + list["overlays"] = (overlayIDs); + + return list; + } + else { + return list; } } bool SelectionScriptingInterface::removeListFromMap(const QString& listName) { - if (_selectedItemsListMap.remove(listName)) { - emit selectedItemsListChanged(listName); + bool removed = false; + { + QWriteLocker lock(&_selectionListsLock); + removed = _selectedItemsListMap.remove(listName); + } + if (removed) { + onSelectedItemsListChanged(listName); return true; } else { return false; } } +void SelectionScriptingInterface::setupHandler(const QString& selectionName) { + QWriteLocker lock(&_selectionHandlersLock); + auto handler = _handlerMap.find(selectionName); + if (handler == _handlerMap.end()) { + handler = _handlerMap.insert(selectionName, new SelectionToSceneHandler()); + } + + (*handler)->initialize(selectionName); +} + +void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) { + { + QWriteLocker lock(&_selectionHandlersLock); + auto handler = _handlerMap.find(listName); + if (handler != _handlerMap.end()) { + (*handler)->updateSceneFromSelectedList(); + } + } + + emit selectedItemsListChanged(listName); +} + SelectionToSceneHandler::SelectionToSceneHandler() { } void SelectionToSceneHandler::initialize(const QString& listName) { _listName = listName; + updateSceneFromSelectedList(); } void SelectionToSceneHandler::selectedItemsListChanged(const QString& listName) { @@ -199,3 +375,85 @@ void SelectionToSceneHandler::updateSceneFromSelectedList() { qWarning() << "SelectionToSceneHandler::updateRendererSelectedList(), Unexpected null scene, possibly during application shutdown"; } } + +bool SelectionHighlightStyle::fromVariantMap(const QVariantMap& properties) { + auto colorVariant = properties["outlineUnoccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._outlineUnoccluded.color = toGlm(color); + } + } + colorVariant = properties["outlineOccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._outlineOccluded.color = toGlm(color); + } + } + colorVariant = properties["fillUnoccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._fillUnoccluded.color = toGlm(color); + } + } + colorVariant = properties["fillOccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._fillOccluded.color = toGlm(color); + } + } + + auto intensityVariant = properties["outlineUnoccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._outlineUnoccluded.alpha = intensityVariant.toFloat(); + } + intensityVariant = properties["outlineOccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._outlineOccluded.alpha = intensityVariant.toFloat(); + } + intensityVariant = properties["fillUnoccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._fillUnoccluded.alpha = intensityVariant.toFloat(); + } + intensityVariant = properties["fillOccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._fillOccluded.alpha = intensityVariant.toFloat(); + } + + auto outlineWidth = properties["outlineWidth"]; + if (outlineWidth.isValid()) { + _style._outlineWidth = outlineWidth.toFloat(); + } + auto isOutlineSmooth = properties["isOutlineSmooth"]; + if (isOutlineSmooth.isValid()) { + _style._isOutlineSmooth = isOutlineSmooth.toBool(); + } + + return true; +} + +QVariantMap SelectionHighlightStyle::toVariantMap() const { + QVariantMap properties; + + properties["outlineUnoccludedColor"] = xColorToVariant(xColorFromGlm(_style._outlineUnoccluded.color)); + properties["outlineOccludedColor"] = xColorToVariant(xColorFromGlm(_style._outlineOccluded.color)); + properties["fillUnoccludedColor"] = xColorToVariant(xColorFromGlm(_style._fillUnoccluded.color)); + properties["fillOccludedColor"] = xColorToVariant(xColorFromGlm(_style._fillOccluded.color)); + + properties["outlineUnoccludedAlpha"] = _style._outlineUnoccluded.alpha; + properties["outlineOccludedAlpha"] = _style._outlineOccluded.alpha; + properties["fillUnoccludedAlpha"] = _style._fillUnoccluded.alpha; + properties["fillOccludedAlpha"] = _style._fillOccluded.alpha; + + properties["outlineWidth"] = _style._outlineWidth; + properties["isOutlineSmooth"] = _style._isOutlineSmooth; + + return properties; +} \ No newline at end of file diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index d9003c2c32..8295375870 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -21,22 +21,23 @@ #include "RenderableEntityItem.h" #include "ui/overlays/Overlay.h" #include +#include class GameplayObjects { public: GameplayObjects(); - bool getContainsData() { return containsData; } + bool getContainsData() const { return containsData; } - std::vector getAvatarIDs() { return _avatarIDs; } + std::vector getAvatarIDs() const { return _avatarIDs; } bool addToGameplayObjects(const QUuid& avatarID); bool removeFromGameplayObjects(const QUuid& avatarID); - std::vector getEntityIDs() { return _entityIDs; } + std::vector getEntityIDs() const { return _entityIDs; } bool addToGameplayObjects(const EntityItemID& entityID); bool removeFromGameplayObjects(const EntityItemID& entityID); - std::vector getOverlayIDs() { return _overlayIDs; } + std::vector getOverlayIDs() const { return _overlayIDs; } bool addToGameplayObjects(const OverlayID& overlayID); bool removeFromGameplayObjects(const OverlayID& overlayID); @@ -48,20 +49,52 @@ private: }; +class SelectionToSceneHandler : public QObject { + Q_OBJECT +public: + SelectionToSceneHandler(); + void initialize(const QString& listName); + + void updateSceneFromSelectedList(); + +public slots: + void selectedItemsListChanged(const QString& listName); + +private: + QString _listName{ "" }; +}; +using SelectionToSceneHandlerPointer = QSharedPointer; + +class SelectionHighlightStyle { +public: + SelectionHighlightStyle() {} + + void setBoundToList(bool bound) { _isBoundToList = bound; } + bool isBoundToList() const { return _isBoundToList; } + + bool fromVariantMap(const QVariantMap& properties); + QVariantMap toVariantMap() const; + + render::HighlightStyle getStyle() const { return _style; } + +protected: + bool _isBoundToList{ false }; + render::HighlightStyle _style; +}; + class SelectionScriptingInterface : public QObject, public Dependency { Q_OBJECT public: SelectionScriptingInterface(); - GameplayObjects getList(const QString& listName); - /**jsdoc - * Prints out the list of avatars, entities and overlays stored in a particular selection. - * @function Selection.printList - * @param listName {string} name of the selection + * Query the names of all the selection lists + * @function Selection.getListNames + * @return An array of names of all the selection lists */ - Q_INVOKABLE void printList(const QString& listName); + Q_INVOKABLE QStringList getListNames() const; + /**jsdoc * Removes a named selection from the list of selections. * @function Selection.removeListFromMap @@ -96,30 +129,103 @@ public: */ Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); + /**jsdoc + * Prints out the list of avatars, entities and overlays stored in a particular selection. + * @function Selection.printList + * @param listName {string} name of the selection + */ + Q_INVOKABLE void printList(const QString& listName); + + /**jsdoc + * Query the list of avatars, entities and overlays stored in a particular selection. + * @function Selection.getList + * @param listName {string} name of the selection + * @return a js object describing the content of a selection list with the following properties: + * - "entities": [ and array of the entityID of the entities in the selection] + * - "avatars": [ and array of the avatarID of the avatars in the selection] + * - "overlays": [ and array of the overlayID of the overlays in the selection] + * If the list name doesn't exist, the function returns an empty js object with no properties. + */ + Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const; + + /**jsdoc + * Query the names of the highlighted selection lists + * @function Selection.getHighlightedListNames + * @return An array of names of the selection list currently highlight enabled + */ + Q_INVOKABLE QStringList getHighlightedListNames() const; + + /**jsdoc + * Enable highlighting for the named selection. + * If the Selection doesn't exist, it will be created. + * All objects in the list will be displayed with the highlight effect as specified from the highlightStyle. + * The function can be called several times with different values in the style to modify it. + * + * @function Selection.enableListHighlight + * @param listName {string} name of the selection + * @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle). + * @returns {bool} true if the selection was successfully enabled for highlight. + */ + Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); + /**jsdoc + * Disable highlighting for the named selection. + * If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false. + * + * @function Selection.disableListHighlight + * @param listName {string} name of the selection + * @returns {bool} true if the selection was successfully disabled for highlight, false otherwise. + */ + Q_INVOKABLE bool disableListHighlight(const QString& listName); + /**jsdoc + * Query the highlight style values for the named selection. + * If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object. + * Otherwise, the jsObject describes the highlight style properties: + * - outlineUnoccludedColor: {xColor} Color of the specified highlight region + * - outlineOccludedColor: {xColor} " + * - fillUnoccludedColor: {xColor} " + * - fillOccludedColor: {xColor} " + * + * - outlineUnoccludedAlpha: {float} Alpha value ranging from 0.0 (not visible) to 1.0 (fully opaque) for the specified highlight region + * - outlineOccludedAlpha: {float} " + * - fillUnoccludedAlpha: {float} " + * - fillOccludedAlpha: {float} " + * + * - outlineWidth: {float} width of the outline expressed in pixels + * - isOutlineSmooth: {bool} true to enable oultine smooth falloff + * + * @function Selection.getListHighlightStyle + * @param listName {string} name of the selection + * @returns {jsObject} highlight style as described above + */ + Q_INVOKABLE QVariantMap getListHighlightStyle(const QString& listName) const; + + + GameplayObjects getList(const QString& listName); + + render::HighlightStyle getHighlightStyle(const QString& listName) const; + + void onSelectedItemsListChanged(const QString& listName); + signals: void selectedItemsListChanged(const QString& listName); private: + mutable QReadWriteLock _selectionListsLock; QMap _selectedItemsListMap; + mutable QReadWriteLock _selectionHandlersLock; + QMap _handlerMap; + + mutable QReadWriteLock _highlightStylesLock; + QMap _highlightStyleMap; + template bool addToGameplayObjects(const QString& listName, T idToAdd); template bool removeFromGameplayObjects(const QString& listName, T idToRemove); -}; + + void setupHandler(const QString& selectionName); -class SelectionToSceneHandler : public QObject { - Q_OBJECT -public: - SelectionToSceneHandler(); - void initialize(const QString& listName); - - void updateSceneFromSelectedList(); - -public slots: - void selectedItemsListChanged(const QString& listName); - -private: - QString _listName { "" }; + }; #endif // hifi_SelectionScriptingInterface_h diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 99fdd5fbde..8b4279af02 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -22,31 +22,3 @@ void WalletScriptingInterface::refreshWalletStatus() { auto wallet = DependencyManager::get(); wallet->getWalletStatus(); } - -static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; -void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href)); - return; - } - - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - - tablet->loadQMLSource(CHECKOUT_QML_PATH); - DependencyManager::get()->openTablet(); - - QQuickItem* root = nullptr; - if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) { - root = DependencyManager::get()->getRootItem(); - } else { - root = tablet->getTabletRoot(); - } - CheckoutProxy* checkout = new CheckoutProxy(root->findChild("checkout")); - - // Example: Wallet.buy("Test Flaregun", "0d90d21c-ce7a-4990-ad18-e9d2cf991027", 17, "http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json"); - checkout->writeProperty("itemName", name); - checkout->writeProperty("itemId", id); - checkout->writeProperty("itemPrice", price); - checkout->writeProperty("itemHref", href); -} \ No newline at end of file diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 038c580197..d7f9d9242e 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -41,8 +41,6 @@ public: Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } void setWalletStatus(const uint& status) { _walletStatus = status; } - Q_INVOKABLE void buy(const QString& name, const QString& id, const int& price, const QString& href); - signals: void walletStatusChanged(); void walletNotSetup(); diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 556399c741..6323ff9dc8 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -35,6 +35,8 @@ public: // getters virtual bool is3D() const override { return true; } + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } + // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 3681f42381..de644f165b 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -72,14 +72,7 @@ ContextOverlayInterface::ContextOverlayInterface() { connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); { - render::Transaction transaction; - initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction); - for (auto i = 1; i < MAX_SELECTION_COUNT; i++) { - auto selectionName = QString("highlightList") + QString::number(i); - initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction); - } - const render::ScenePointer& scene = qApp->getMain3DScene(); - scene->enqueueTransaction(transaction); + _selectionScriptingInterface->enableListHighlight("contextOverlayHighlightList", QVariantMap()); } auto nodeList = DependencyManager::get(); @@ -88,12 +81,6 @@ ContextOverlayInterface::ContextOverlayInterface() { _challengeOwnershipTimeoutTimer.setSingleShot(true); } -void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) { - handler.initialize(selectionName); - connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged); - transaction.resetSelectionHighlight(selectionName.toStdString()); -} - static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 }; static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters static const float CONTEXT_OVERLAY_SIZE = 0.09f; // in meters, same x and y dims diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 81e398e15d..990a7fe599 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -96,9 +96,6 @@ private: void disableEntityHighlight(const EntityItemID& entityItemID); void deletingEntity(const EntityItemID& entityItemID); - void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction); - - SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT]; Q_INVOKABLE void startChallengeOwnershipTimer(); QTimer _challengeOwnershipTimeoutTimer; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index f27e26280a..0846599728 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -79,6 +79,12 @@ void ModelOverlay::update(float deltatime) { if (_model->needsFixupInScene()) { _model->removeFromScene(scene, transaction); _model->addToScene(scene, transaction); + + auto newRenderItemIDs{ _model->fetchRenderItemIDs() }; + transaction.updateItem(getRenderItemID(), [newRenderItemIDs](Overlay& data) { + auto modelOverlay = static_cast(&data); + modelOverlay->setSubRenderItemIDs(newRenderItemIDs); + }); } if (_visibleDirty) { _visibleDirty = false; @@ -104,6 +110,10 @@ bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePoint void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::removeFromScene(overlay, scene, transaction); _model->removeFromScene(scene, transaction); + transaction.updateItem(getRenderItemID(), [](Overlay& data) { + auto modelOverlay = static_cast(&data); + modelOverlay->clearSubRenderItemIDs(); + }); } void ModelOverlay::setVisible(bool visible) { @@ -529,3 +539,19 @@ void ModelOverlay::copyAnimationJointDataToModel(QVector jointsData) _updateModel = true; } +void ModelOverlay::clearSubRenderItemIDs() { + _subRenderItemIDs.clear(); +} + +void ModelOverlay::setSubRenderItemIDs(const render::ItemIDs& ids) { + _subRenderItemIDs = ids; +} + +uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { + if (_model) { + auto metaSubItems = _subRenderItemIDs; + subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end()); + return (uint32_t)metaSubItems.size(); + } + return 0; +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index c4506d9621..4f7f1e0cae 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -30,6 +30,12 @@ public: virtual void update(float deltatime) override; virtual void render(RenderArgs* args) override {}; + + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override; + + void clearSubRenderItemIDs(); + void setSubRenderItemIDs(const render::ItemIDs& ids); + void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, @@ -74,6 +80,8 @@ private: ModelPointer _model; QVariantMap _modelTextures; + render::ItemIDs _subRenderItemIDs; + QUrl _url; bool _updateModel { false }; bool _scaleToFit { false }; diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 39208f01a0..806fc1aa14 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -53,6 +53,8 @@ public: virtual const render::ShapeKey getShapeKey() { return render::ShapeKey::Builder::ownPipeline(); } + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } + // getters virtual QString getType() const = 0; virtual bool is3D() const = 0; @@ -130,6 +132,7 @@ namespace render { template <> int payloadGetLayer(const Overlay::Pointer& overlay); template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay); + template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems); } Q_DECLARE_METATYPE(OverlayID); diff --git a/interface/src/ui/overlays/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h index a1efe8a6de..3175df92f1 100644 --- a/interface/src/ui/overlays/Overlay2D.h +++ b/interface/src/ui/overlays/Overlay2D.h @@ -26,6 +26,8 @@ public: virtual bool is3D() const override { return false; } + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return 1; } + // getters int getX() const { return _bounds.x(); } int getY() const { return _bounds.y(); } diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 8d3e514a0f..fceb261503 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -87,4 +87,10 @@ namespace render { template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay) { return overlay->getShapeKey(); } + + + template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems) { + return overlay->fetchMetaSubItems(subItems); + } } + diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index 035903c6cb..e4405d91e3 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -318,7 +318,6 @@ void FBXBaker::rewriteAndBakeSceneTextures() { if (textureChild.name == "RelativeFilename") { QString fbxTextureFileName { textureChild.properties.at(0).toString() }; - // Callback to get texture type // grab the ID for this texture so we can figure out the // texture type from the loaded materials auto textureID { object->properties[0].toString() }; diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 5d04c17688..a6f366562b 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -255,7 +255,7 @@ QString ModelBaker::compressTexture(QString modelTextureFileName, image::Texture return QString::null; } - if (!TextureBaker::getSupportedFormats().contains(modelTextureFileInfo.suffix())) { + if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) { // this is a texture format we don't bake, skip it handleWarning(modelTextureFileName + " is not a bakeable texture format"); return QString::null; diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 1a320efabc..b6edd07965 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -61,14 +61,6 @@ void TextureBaker::abort() { _abortProcessing.store(true); } -const QStringList TextureBaker::getSupportedFormats() { - auto formats = QImageReader::supportedImageFormats(); - QStringList stringFormats; - std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), - [](QByteArray& format) -> QString { return format; }); - return stringFormats; -} - void TextureBaker::loadTexture() { // check if the texture is local or first needs to be downloaded if (_textureURL.isLocalFile()) { @@ -121,8 +113,15 @@ void TextureBaker::handleTextureNetworkReply() { } void TextureBaker::processTexture() { - auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(), + // the baked textures need to have the source hash added for cache checks in Interface + // so we add that to the processed texture before handling it off to be serialized + auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); + std::string hash = hashData.toHex().toStdString(); + + // IMPORTANT: _originalTexture is empty past this point + auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(), ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing); + processedTexture->setSourceHash(hash); if (shouldStop()) { return; @@ -133,11 +132,6 @@ void TextureBaker::processTexture() { return; } - // the baked textures need to have the source hash added for cache checks in Interface - // so we add that to the processed texture before handling it off to be serialized - auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); - std::string hash = hashData.toHex().toStdString(); - processedTexture->setSourceHash(hash); auto memKTX = gpu::Texture::serialize(*processedTexture); diff --git a/libraries/baking/src/TextureBaker.h b/libraries/baking/src/TextureBaker.h index b2e86b2b5b..90ecfe52f7 100644 --- a/libraries/baking/src/TextureBaker.h +++ b/libraries/baking/src/TextureBaker.h @@ -31,8 +31,6 @@ public: const QDir& outputDirectory, const QString& bakedFilename = QString(), const QByteArray& textureContent = QByteArray()); - static const QStringList getSupportedFormats(); - const QByteArray& getOriginalTexture() const { return _originalTexture; } QUrl getTextureURL() const { return _textureURL; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 06b81ff428..c992eb5dc4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1149,7 +1149,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin if (model && model->isLoaded()) { if (!entity->_dimensionsInitialized || entity->_needsInitialSimulation) { return true; - } + } // Check to see if we need to update the model bounds if (entity->needsUpdateModelBounds()) { diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 9f584c844f..9e4ab2e43f 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -75,6 +75,14 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { namespace image { +const QStringList getSupportedFormats() { + auto formats = QImageReader::supportedImageFormats(); + QStringList stringFormats; + std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), + [](QByteArray& format) -> QString { return format; }); + return stringFormats; +} + QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { @@ -110,64 +118,64 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } } -gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(srcImage, srcImageName, true, abortProcessing); + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } @@ -238,33 +246,43 @@ uint32 packR11G11B10F(const glm::vec3& color) { return glm::packF2x11_1x10(ucolor); } -gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, - int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing) { +QImage processRawImageData(QByteArray&& content, const std::string& filename) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QByteArray localCopy = std::move(content); + // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); QBuffer buffer; - buffer.setData(content); + buffer.setData(localCopy); QImageReader imageReader(&buffer, filenameExtension.c_str()); - QImage image; if (imageReader.canRead()) { - image = imageReader.read(); + return imageReader.read(); } else { // Extension could be incorrect, try to detect the format from the content QImageReader newImageReader; newImageReader.setDecideFormatFromContent(true); - buffer.setData(content); + buffer.setData(localCopy); newImageReader.setDevice(&buffer); if (newImageReader.canRead()) { qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str() << "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)"; - image = newImageReader.read(); + return newImageReader.read(); } } + return QImage(); +} + +gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename, + int maxNumPixels, TextureUsage::Type textureType, + const std::atomic& abortProcessing) { + + QImage image = processRawImageData(std::move(content), filename); + int imageWidth = image.width(); int imageHeight = image.height(); @@ -282,22 +300,26 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f int originalHeight = imageHeight; imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); - QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - image.swap(newImage); + image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); qCDebug(imagelogging).nospace() << "Downscaled " << filename.c_str() << " (" << QSize(originalWidth, originalHeight) << " to " << QSize(imageWidth, imageHeight) << ")"; } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(image, filename, abortProcessing); + auto texture = loader(std::move(image), filename, abortProcessing); return texture; } -QImage processSourceImage(const QImage& srcImage, bool cubemap) { +QImage processSourceImage(QImage&& srcImage, bool cubemap) { PROFILE_RANGE(resource_parse, "processSourceImage"); - const glm::uvec2 srcImageSize = toGlm(srcImage.size()); + + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(srcImage); + + const glm::uvec2 srcImageSize = toGlm(localCopy.size()); glm::uvec2 targetSize = srcImageSize; while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) { @@ -319,10 +341,10 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { if (targetSize != srcImageSize) { PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; - return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - return srcImage; + return localCopy; } #if defined(NVTT_API) @@ -429,10 +451,14 @@ public: } }; -void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic& abortProcessing, int face) { - assert(image.format() == QIMAGE_HDR_FORMAT); +void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(image); - const int width = image.width(), height = image.height(); + assert(localCopy.format() == QIMAGE_HDR_FORMAT); + + const int width = localCopy.width(), height = localCopy.height(); std::vector data; std::vector::iterator dataIt; auto mipFormat = texture->getStoredMipFormat(); @@ -471,10 +497,10 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom return; } - data.resize(width*height); + data.resize(width * height); dataIt = data.begin(); for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = reinterpret_cast( image.constScanLine(lineNb) ); + const uint32* srcPixelIt = reinterpret_cast(localCopy.constScanLine(lineNb)); const uint32* srcPixelEnd = srcPixelIt + width; while (srcPixelIt < srcPixelEnd) { @@ -485,6 +511,9 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom } assert(dataIt == data.end()); + // We're done with the localCopy, free up the memory to avoid bloating the heap + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); std::unique_ptr outputHandler; @@ -497,7 +526,7 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); } else { - outputHandler.reset( new OutputHandler(texture, face) ); + outputHandler.reset(new OutputHandler(texture, face)); } outputOptions.setOutputHandler(outputHandler.get()); @@ -518,13 +547,17 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom } } -void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing, int face) { - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); +void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(image); + + if (localCopy.format() != QImage::Format_ARGB32) { + localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); } - const int width = image.width(), height = image.height(); - const void* data = static_cast(image.constBits()); + const int width = localCopy.width(), height = localCopy.height(); + const void* data = static_cast(localCopy.constBits()); nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; @@ -537,7 +570,11 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { +void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); if (image.format() == QIMAGE_HDR_FORMAT) { - generateHDRMips(texture, image, abortProcessing, face); + generateHDRMips(texture, std::move(image), abortProcessing, face); } else { - generateLDRMips(texture, image, abortProcessing, face); + generateLDRMips(texture, std::move(image), abortProcessing, face); } #else texture->setAutoGenerateMips(true); @@ -682,10 +719,11 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs validAlpha = (numOpaques != NUM_PIXELS); } -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); + bool validAlpha = image.hasAlphaChannel(); bool alphaAsMask = false; @@ -731,7 +769,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s } theTexture->setUsage(usage.build()); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; @@ -749,16 +787,20 @@ double mapComponent(double sobelValue) { return (sobelValue + 1.0) * factor; } -QImage processBumpMap(QImage& image) { - if (image.format() != QImage::Format_Grayscale8) { - image = image.convertToFormat(QImage::Format_Grayscale8); +QImage processBumpMap(QImage&& image) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(image); + + if (localCopy.format() != QImage::Format_Grayscale8) { + localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8); } // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image const double pStrength = 2.0; - int width = image.width(); - int height = image.height(); + int width = localCopy.width(); + int height = localCopy.height(); QImage result(width, height, QImage::Format_ARGB32); @@ -771,14 +813,14 @@ QImage processBumpMap(QImage& image) { const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); // surrounding pixels - const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped); - const QRgb top = image.pixel(iPrevClamped, j); - const QRgb topRight = image.pixel(iPrevClamped, jNextClamped); - const QRgb right = image.pixel(i, jNextClamped); - const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped); - const QRgb bottom = image.pixel(iNextClamped, j); - const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped); - const QRgb left = image.pixel(i, jPrevClamped); + const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped); + const QRgb top = localCopy.pixel(iPrevClamped, j); + const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped); + const QRgb right = localCopy.pixel(i, jNextClamped); + const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped); + const QRgb bottom = localCopy.pixel(iNextClamped, j); + const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped); + const QRgb left = localCopy.pixel(i, jPrevClamped); // take their gray intensities // since it's a grayscale image, the value of each component RGB is the same @@ -807,13 +849,13 @@ QImage processBumpMap(QImage& image) { return result; } -gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); if (isBumpMap) { - image = processBumpMap(image); + image = processBumpMap(std::move(image)); } // Make sure the normal map source image is ARGB32 @@ -833,17 +875,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImag theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; } -gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); @@ -869,7 +911,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImag theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; @@ -939,7 +981,7 @@ public: static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { QImage image(faceWidth, faceWidth, source.format()); - glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height()); + glm::vec2 dstInvSize(1.0f / (float)source.width(), 1.0f / (float)source.height()); struct CubeToXYZ { gpu::Texture::CubeFace _face; @@ -1142,8 +1184,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) //#define DEBUG_COLOR_PACKING -QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { - QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); +QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(srcImage); + + QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT); std::function packFunc; #ifdef DEBUG_COLOR_PACKING std::function unpackFunc; @@ -1165,13 +1211,13 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { default: qCWarning(imagelogging) << "Unsupported HDR format"; Q_UNREACHABLE(); - return srcImage; + return localCopy; } - srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); - for (auto y = 0; y < srcImage.height(); y++) { - const QRgb* srcLineIt = reinterpret_cast( srcImage.constScanLine(y) ); - const QRgb* srcLineEnd = srcLineIt + srcImage.width(); + localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); + for (auto y = 0; y < localCopy.height(); y++) { + const QRgb* srcLineIt = reinterpret_cast( localCopy.constScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + localCopy.width(); uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); glm::vec3 color; @@ -1196,86 +1242,99 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { return hdrImage; } -gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(srcImage); + + int originalWidth = localCopy.width(); + int originalHeight = localCopy.height(); + if ((originalWidth <= 0) && (originalHeight <= 0)) { + return nullptr; + } + gpu::TexturePointer theTexture = nullptr; - if ((srcImage.width() > 0) && (srcImage.height() > 0)) { - QImage image = processSourceImage(srcImage, true); - if (image.format() != QIMAGE_HDR_FORMAT) { - image = convertToHDRFormat(image, HDR_FORMAT); + QImage image = processSourceImage(std::move(localCopy), true); + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(std::move(image), HDR_FORMAT); + } + + gpu::Element formatMip; + gpu::Element formatGPU; + if (isCubeTexturesCompressionEnabled()) { + formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + } else { + formatMip = HDR_FORMAT; + formatGPU = HDR_FORMAT; + } + + // Find the layout of the cubemap in the 2D image + // Use the original image size since processSourceImage may have altered the size / aspect ratio + int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); + + if (foundLayout < 0) { + qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); + return nullptr; + } + + std::vector faces; + + // If found, go extract the faces as separate images + auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; + if (layout._type == CubeLayout::FLAT) { + int faceWidth = image.width() / layout._widthRatio; + + faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); + } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { + // THe face width is estimated from the input image + const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; + const int EQUIRECT_MAX_FACE_WIDTH = 2048; + int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); + for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { + QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); + faces.push_back(std::move(faceImage)); } + } - gpu::Element formatMip; - gpu::Element formatGPU; - if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - } else { - formatMip = HDR_FORMAT; - formatGPU = HDR_FORMAT; - } + // free up the memory afterward to avoid bloating the heap + image = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - // Find the layout of the cubemap in the 2D image - // Use the original image size since processSourceImage may have altered the size / aspect ratio - int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height()); - - std::vector faces; - // If found, go extract the faces as separate images - if (foundLayout >= 0) { - auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; - if (layout._type == CubeLayout::FLAT) { - int faceWidth = image.width() / layout._widthRatio; - - faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); - } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { - // THe face width is estimated from the input image - const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; - const int EQUIRECT_MAX_FACE_WIDTH = 2048; - int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); - for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { - QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth); - faces.push_back(faceImage); - } - } - } else { - qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); - return nullptr; - } - - // If the 6 faces have been created go on and define the true Texture - if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { - theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); + // If the 6 faces have been created go on and define the true Texture + if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { + theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + // Generate irradiance while we are at it + if (generateIrradiance) { + PROFILE_RANGE(resource_parse, "generateIrradiance"); + auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + irradianceTexture->setSource(srcImageName); + irradianceTexture->setStoredMipFormat(HDR_FORMAT); for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), faces[face], abortProcessing, face); + irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } - // Generate irradiance while we are at it - if (generateIrradiance) { - PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(HDR_FORMAT); - for (uint8 face = 0; face < faces.size(); ++face) { - irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); - } + irradianceTexture->generateIrradiance(); - irradianceTexture->generateIrradiance(); + auto irradiance = irradianceTexture->getIrradiance(); + theTexture->overrideIrradiance(irradiance); + } - auto irradiance = irradianceTexture->getIrradiance(); - theTexture->overrideIrradiance(irradiance); - } + for (uint8 face = 0; face < faces.size(); ++face) { + generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face); } } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 856dc009cf..39f5ea3bab 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -41,45 +41,47 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); -gpu::TexturePointer create2DTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict, +gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap, +gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels, +gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance, +gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage +const QStringList getSupportedFormats(); + bool isColorTexturesCompressionEnabled(); bool isNormalTexturesCompressionEnabled(); bool isGrayscaleTexturesCompressionEnabled(); @@ -90,7 +92,7 @@ void setNormalTexturesCompressionEnabled(bool enabled); void setGrayscaleTexturesCompressionEnabled(bool enabled); void setCubeTexturesCompressionEnabled(bool enabled); -gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, +gpu::TexturePointer processImage(QByteArray&& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, const std::atomic& abortProcessing = false); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index d25c5225d6..b8f81dc7a4 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -264,7 +264,7 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString(), false)); + return gpu::TexturePointer(loader(std::move(image), QUrl::fromLocalFile(path).fileName().toStdString(), false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -954,7 +954,9 @@ void ImageReader::read() { gpu::TexturePointer texture; { PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0); - texture = image::processImage(_content, _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + + // IMPORTANT: _content is empty past this point + texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index 2faa10682e..f4d4ad0e04 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -16,7 +16,7 @@ <@include Highlight_shared.slh@> uniform highlightParamsBuffer { - HighlightParameters params; + HighlightParameters params; }; uniform sampler2D sceneDepthMap; @@ -35,8 +35,7 @@ void main(void) { // We offset by half a texel to be centered on the depth sample. If we don't do this // the blur will have a different width between the left / right sides and top / bottom // sides of the silhouette - float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x; - float intensity = 0.0; + float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x; if (highlightedDepth < FAR_Z) { // We're not on the far plane so we are on the highlighted object, thus no outline to do! @@ -47,10 +46,13 @@ void main(void) { highlightedDepth = -evalZeyeFromZdb(highlightedDepth); sceneDepth = -evalZeyeFromZdb(sceneDepth); - // Are we occluded? - intensity = sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS) ? params._occludedFillOpacity : params._unoccludedFillOpacity; + if (sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS)) { + outFragColor = vec4(params._fillOccludedColor, params._fillOccludedAlpha); + } else { + outFragColor = vec4(params._fillUnoccludedColor, params._fillUnoccludedAlpha); + } <@else@> - discard; + discard; <@endif@> } else { vec2 halfTexel = getInvWidthHeight() / 2; @@ -62,6 +64,10 @@ void main(void) { int x; int y; + float intensity = 0.0; + float outlinedDepth = 0.0; + float sumOutlineDepth = 0.0; + for (y=0 ; y=0.0 && uv.x<=1.0) { - highlightedDepth = texture(highlightedDepthMap, uv).x; - intensity += (highlightedDepth < FAR_Z) ? 1.0 : 0.0; + outlinedDepth = texture(highlightedDepthMap, uv).x; + float touch = (outlinedDepth < FAR_Z) ? 1.0 : 0.0; + sumOutlineDepth = max(outlinedDepth * touch, sumOutlineDepth); + intensity += touch; weight += 1.0; } uv.x += deltaUv.x; @@ -79,15 +87,32 @@ void main(void) { } } + if (intensity > 0) { + // sumOutlineDepth /= intensity; + } else { + sumOutlineDepth = FAR_Z; + } + intensity /= weight; if (intensity < OPACITY_EPSILON) { discard; } + intensity = min(1.0, intensity / params._threshold); - intensity = min(1.0, intensity / params._threshold) * params._intensity; + // But we need to check the scene depth against the depth of the outline + float sceneDepth = texture(sceneDepthMap, texCoord0).x; + + // Transform to linear depth for better precision + outlinedDepth = -evalZeyeFromZdb(sumOutlineDepth); + sceneDepth = -evalZeyeFromZdb(sceneDepth); + + // Are we occluded? + if (sceneDepth < (outlinedDepth/*-LINEAR_DEPTH_BIAS*/)) { + outFragColor = vec4(params._outlineOccludedColor, intensity * params._outlineOccludedAlpha); + } else { + outFragColor = vec4(params._outlineUnoccludedColor, intensity * params._outlineUnoccludedAlpha); + } } - - outFragColor = vec4(params._color.rgb, intensity); } <@endfunc@> diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 7c58e5ba66..fee1f4a568 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -88,7 +88,7 @@ HighlightSharedParameters::HighlightSharedParameters() { } float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight) { - return ceilf(style.outlineWidth * frameBufferHeight / 400.0f); + return ceilf(style._outlineWidth * frameBufferHeight / 400.0f); } PrepareDrawHighlight::PrepareDrawHighlight() { @@ -267,14 +267,19 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const { auto& shaderParameters = _configuration.edit(); - shaderParameters._color = highlight._style.color; - shaderParameters._intensity = highlight._style.outlineIntensity * (highlight._style.isOutlineSmooth ? 2.0f : 1.0f); - shaderParameters._unoccludedFillOpacity = highlight._style.unoccludedFillOpacity; - shaderParameters._occludedFillOpacity = highlight._style.occludedFillOpacity; - shaderParameters._threshold = highlight._style.isOutlineSmooth ? 1.0f : 1e-3f; - shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style.outlineWidth * 3 + 0.5f))); + shaderParameters._outlineUnoccludedColor = highlight._style._outlineUnoccluded.color; + shaderParameters._outlineUnoccludedAlpha = highlight._style._outlineUnoccluded.alpha * (highlight._style._isOutlineSmooth ? 2.0f : 1.0f); + shaderParameters._outlineOccludedColor = highlight._style._outlineOccluded.color; + shaderParameters._outlineOccludedAlpha = highlight._style._outlineOccluded.alpha * (highlight._style._isOutlineSmooth ? 2.0f : 1.0f); + shaderParameters._fillUnoccludedColor = highlight._style._fillUnoccluded.color; + shaderParameters._fillUnoccludedAlpha = highlight._style._fillUnoccluded.alpha; + shaderParameters._fillOccludedColor = highlight._style._fillOccluded.color; + shaderParameters._fillOccludedAlpha = highlight._style._fillOccluded.alpha; + + shaderParameters._threshold = highlight._style._isOutlineSmooth ? 1.0f : 1e-3f; + shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style._outlineWidth * 3 + 0.5f))); // Size is in normalized screen height. We decide that for highlight width = 1, this is equal to 1/400. - auto size = highlight._style.outlineWidth / 400.0f; + auto size = highlight._style._outlineWidth / 400.0f; shaderParameters._size.x = (size * framebufferSize.y) / framebufferSize.x; shaderParameters._size.y = size; } @@ -432,19 +437,20 @@ void SelectionToHighlight::run(const render::RenderContextPointer& renderContext outputs.clear(); _sharedParameters->_highlightIds.fill(render::HighlightStage::INVALID_INDEX); - for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { - std::ostringstream stream; - if (i > 0) { - stream << "highlightList" << i; - } else { - stream << "contextOverlayHighlightList"; - } - auto selectionName = stream.str(); - if (!scene->isSelectionEmpty(selectionName)) { - auto highlightId = highlightStage->getHighlightIdBySelection(selectionName); - if (!render::HighlightStage::isIndexInvalid(highlightId)) { - _sharedParameters->_highlightIds[outputs.size()] = highlightId; - outputs.emplace_back(selectionName); + int numLayers = 0; + auto highlightList = highlightStage->getActiveHighlightIds(); + + for (auto styleId : highlightList) { + auto highlight = highlightStage->getHighlight(styleId); + + if (!scene->isSelectionEmpty(highlight._selectionName)) { + auto highlightId = highlightStage->getHighlightIdBySelection(highlight._selectionName); + _sharedParameters->_highlightIds[outputs.size()] = highlightId; + outputs.emplace_back(highlight._selectionName); + numLayers++; + + if (numLayers == HighlightSharedParameters::MAX_PASS_COUNT) { + break; } } } diff --git a/libraries/render-utils/src/Highlight_shared.slh b/libraries/render-utils/src/Highlight_shared.slh index 5efbde4d52..edc51e4ecb 100644 --- a/libraries/render-utils/src/Highlight_shared.slh +++ b/libraries/render-utils/src/Highlight_shared.slh @@ -11,17 +11,18 @@ struct HighlightParameters { - TVEC3 _color; - float _intensity; + TVEC3 _outlineUnoccludedColor; + float _outlineUnoccludedAlpha; + TVEC3 _outlineOccludedColor; + float _outlineOccludedAlpha; + TVEC3 _fillUnoccludedColor; + float _fillUnoccludedAlpha; + TVEC3 _fillOccludedColor; + float _fillOccludedAlpha; - TVEC2 _size; - float _unoccludedFillOpacity; - float _occludedFillOpacity; - - float _threshold; int _blurKernelSize; - float padding2; - float padding3; + float _threshold; + TVEC2 _size; }; // <@if 1@> diff --git a/libraries/render/src/render/HighlightStage.cpp b/libraries/render/src/render/HighlightStage.cpp index ade3844321..c9f097b387 100644 --- a/libraries/render/src/render/HighlightStage.cpp +++ b/libraries/render/src/render/HighlightStage.cpp @@ -61,42 +61,42 @@ void HighlightStageConfig::setSelectionName(const QString& name) { } void HighlightStageConfig::setOutlineSmooth(bool isSmooth) { - editStyle().isOutlineSmooth = isSmooth; + editStyle()._isOutlineSmooth = isSmooth; emit dirty(); } void HighlightStageConfig::setColorRed(float value) { - editStyle().color.r = value; + editStyle()._outlineUnoccluded.color.r = value; emit dirty(); } void HighlightStageConfig::setColorGreen(float value) { - editStyle().color.g = value; + editStyle()._outlineUnoccluded.color.g = value; emit dirty(); } void HighlightStageConfig::setColorBlue(float value) { - editStyle().color.b = value; + editStyle()._outlineUnoccluded.color.b = value; emit dirty(); } void HighlightStageConfig::setOutlineWidth(float value) { - editStyle().outlineWidth = value; + editStyle()._outlineWidth = value; emit dirty(); } void HighlightStageConfig::setOutlineIntensity(float value) { - editStyle().outlineIntensity = value; + editStyle()._outlineUnoccluded.alpha = value; emit dirty(); } void HighlightStageConfig::setUnoccludedFillOpacity(float value) { - editStyle().unoccludedFillOpacity = value; + editStyle()._fillUnoccluded.alpha = value; emit dirty(); } void HighlightStageConfig::setOccludedFillOpacity(float value) { - editStyle().occludedFillOpacity = value; + editStyle()._fillOccluded.alpha = value; emit dirty(); } diff --git a/libraries/render/src/render/HighlightStage.h b/libraries/render/src/render/HighlightStage.h index b35fff654c..5e6574840f 100644 --- a/libraries/render/src/render/HighlightStage.h +++ b/libraries/render/src/render/HighlightStage.h @@ -51,6 +51,7 @@ namespace render { HighlightIdList::iterator begin() { return _activeHighlightIds.begin(); } HighlightIdList::iterator end() { return _activeHighlightIds.end(); } + const HighlightIdList& getActiveHighlightIds() const { return _activeHighlightIds; } private: @@ -82,28 +83,28 @@ namespace render { QString getSelectionName() const { return QString(_selectionName.c_str()); } void setSelectionName(const QString& name); - bool isOutlineSmooth() const { return getStyle().isOutlineSmooth; } + bool isOutlineSmooth() const { return getStyle()._isOutlineSmooth; } void setOutlineSmooth(bool isSmooth); - float getColorRed() const { return getStyle().color.r; } + float getColorRed() const { return getStyle()._outlineUnoccluded.color.r; } void setColorRed(float value); - float getColorGreen() const { return getStyle().color.g; } + float getColorGreen() const { return getStyle()._outlineUnoccluded.color.g; } void setColorGreen(float value); - float getColorBlue() const { return getStyle().color.b; } + float getColorBlue() const { return getStyle()._outlineUnoccluded.color.b; } void setColorBlue(float value); - float getOutlineWidth() const { return getStyle().outlineWidth; } + float getOutlineWidth() const { return getStyle()._outlineWidth; } void setOutlineWidth(float value); - float getOutlineIntensity() const { return getStyle().outlineIntensity; } + float getOutlineIntensity() const { return getStyle()._outlineUnoccluded.alpha; } void setOutlineIntensity(float value); - float getUnoccludedFillOpacity() const { return getStyle().unoccludedFillOpacity; } + float getUnoccludedFillOpacity() const { return getStyle()._fillUnoccluded.alpha; } void setUnoccludedFillOpacity(float value); - float getOccludedFillOpacity() const { return getStyle().occludedFillOpacity; } + float getOccludedFillOpacity() const { return getStyle()._fillOccluded.alpha; } void setOccludedFillOpacity(float value); std::string _selectionName{ "contextOverlayHighlightList" }; diff --git a/libraries/render/src/render/HighlightStyle.h b/libraries/render/src/render/HighlightStyle.h index 6e7373c78b..8bef5c33c3 100644 --- a/libraries/render/src/render/HighlightStyle.h +++ b/libraries/render/src/render/HighlightStyle.h @@ -20,17 +20,24 @@ namespace render { // This holds the configuration for a particular outline style class HighlightStyle { public: + struct RGBA { + glm::vec3 color{ 1.0f, 0.7f, 0.2f }; + float alpha{ 0.9f }; + + RGBA(const glm::vec3& c, float a) : color(c), alpha(a) {} + }; + + RGBA _outlineUnoccluded{ { 1.0f, 0.7f, 0.2f }, 0.9f }; + RGBA _outlineOccluded{ { 1.0f, 0.7f, 0.2f }, 0.9f }; + RGBA _fillUnoccluded{ { 0.2f, 0.7f, 1.0f }, 0.0f }; + RGBA _fillOccluded{ { 0.2f, 0.7f, 1.0f }, 0.0f }; + + float _outlineWidth{ 2.0f }; + bool _isOutlineSmooth{ false }; bool isFilled() const { - return unoccludedFillOpacity > 5e-3f || occludedFillOpacity > 5e-3f; + return _fillUnoccluded.alpha > 5e-3f || _fillOccluded.alpha > 5e-3f; } - - glm::vec3 color{ 1.f, 0.7f, 0.2f }; - float outlineWidth{ 2.0f }; - float outlineIntensity{ 0.9f }; - float unoccludedFillOpacity{ 0.0f }; - float occludedFillOpacity{ 0.0f }; - bool isOutlineSmooth{ false }; }; } diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 4b337a1046..bc75e5ad21 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -532,7 +532,7 @@ bool Scene::isSelectionEmpty(const Selection::Name& name) const { std::unique_lock lock(_selectionsMutex); auto found = _selections.find(name); if (found == _selections.end()) { - return false; + return true; } else { return (*found).second.isEmpty(); } diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 39fec45d90..ff1d29eed1 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -432,6 +432,12 @@ glm::vec3 toGlm(const xColor& color) { return glm::vec3(color.red, color.green, color.blue) / MAX_COLOR; } +xColor xColorFromGlm(const glm::vec3 & color) { + static const float MAX_COLOR = 255.0f; + return { (uint8_t)(color.x * MAX_COLOR), (uint8_t)(color.y * MAX_COLOR), (uint8_t)(color.z * MAX_COLOR) }; +} + + glm::vec4 toGlm(const QColor& color) { return glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 7248f4cb46..973998b927 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -177,6 +177,8 @@ vec4 toGlm(const QColor& color); ivec4 toGlm(const QRect& rect); vec4 toGlm(const xColor& color, float alpha); +xColor xColorFromGlm(const glm::vec3 & c); + QSize fromGlm(const glm::ivec2 & v); QMatrix4x4 fromGlm(const glm::mat4 & m); diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 46613f1283..6f2e7d8b19 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -19,8 +19,14 @@ #include #include #include +#include #include // std::once #include "shared/GlobalAppProperties.h" +#include "SharedUtil.h" + +// Format: AppName-PID-Timestamp +// Example: ... +QString TEMP_DIR_FORMAT { "%1-%2-%3" }; const QString& PathUtils::resourcesPath() { #ifdef Q_OS_MAC @@ -60,7 +66,8 @@ QString PathUtils::generateTemporaryDir() { QString appName = qApp->applicationName(); for (auto i = 0; i < 64; ++i) { auto now = std::chrono::system_clock::now().time_since_epoch().count(); - QDir tempDir = rootTempDir.filePath(appName + "-" + QString::number(now)); + auto dirName = TEMP_DIR_FORMAT.arg(appName).arg(qApp->applicationPid()).arg(now); + QDir tempDir = rootTempDir.filePath(dirName); if (tempDir.mkpath(".")) { return tempDir.absolutePath(); } @@ -68,6 +75,39 @@ QString PathUtils::generateTemporaryDir() { return ""; } +// Delete all temporary directories for an application +int PathUtils::removeTemporaryApplicationDirs(QString appName) { + if (appName.isNull()) { + appName = qApp->applicationName(); + } + + auto dirName = TEMP_DIR_FORMAT.arg(appName).arg("*").arg("*"); + + QDir rootTempDir = QDir::tempPath(); + auto dirs = rootTempDir.entryInfoList({ dirName }, QDir::Dirs); + int removed = 0; + for (auto& dir : dirs) { + auto dirName = dir.fileName(); + auto absoluteDirPath = QDir(dir.absoluteFilePath()); + QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?\\d+)\\-(?\\d+)$" }; + + auto match = re.match(dirName); + if (match.hasMatch()) { + auto pid = match.capturedRef("pid").toLongLong(); + auto timestamp = match.capturedRef("timestamp"); + if (!processIsRunning(pid)) { + qDebug() << " Removing old temporary directory: " << dir.absoluteFilePath(); + absoluteDirPath.removeRecursively(); + removed++; + } else { + qDebug() << " Not removing (process is running): " << dir.absoluteFilePath(); + } + } + } + + return removed; +} + QString fileNameWithoutExtension(const QString& fileName, const QVector possibleExtensions) { QString fileNameLowered = fileName.toLower(); foreach (const QString possibleExtension, possibleExtensions) { diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 8c4bcf2394..f3ba5910c4 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -39,6 +39,8 @@ public: static QString generateTemporaryDir(); + static int removeTemporaryApplicationDirs(QString appName = QString::null); + static Qt::CaseSensitivity getFSCaseSensitivity(); static QString stripFilename(const QUrl& url); // note: this is FS-case-sensitive version of parentURL.isParentOf(childURL) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 38a7a3165f..2d2ec7c28f 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -44,6 +44,12 @@ extern "C" FILE * __cdecl __iob_func(void) { #include #endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) +#include +#include +#endif + #include #include #include @@ -1078,6 +1084,24 @@ void setMaxCores(uint8_t maxCores) { #endif } +bool processIsRunning(int64_t pid) { +#ifdef Q_OS_WIN + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (process) { + DWORD exitCode; + if (GetExitCodeProcess(process, &exitCode) != 0) { + return exitCode == STILL_ACTIVE; + } + } + return false; +#else + if (kill(pid, 0) == -1) { + return errno != ESRCH; + } + return true; +#endif +} + void quitWithParentProcess() { if (qApp) { qDebug() << "Parent process died, quitting"; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 25051d45ac..6cf5a4755d 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -238,6 +238,8 @@ void setMaxCores(uint8_t maxCores); const QString PARENT_PID_OPTION = "parent-pid"; void watchParentProcess(int parentPID); +bool processIsRunning(int64_t pid); + #ifdef Q_OS_WIN void* createProcessGroup(); diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml new file mode 100644 index 0000000000..15d7f9fcc9 --- /dev/null +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -0,0 +1,212 @@ +// +// Color.qml +// +// Created by Sam Gateau 12/4/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + + +Item { + HifiConstants { id: hifi } + id: root + + height: 24 + + property var _color: Qt.rgba(1.0, 1.0, 1.0, 1.0 ); + property var zoneWidth: width / 3; + property var hoveredOn: 0.0; + property var sliderHeight: height / 2; + + signal newColor(color __color) + + function setColor(color) { + _color = Qt.rgba(color.r, color.g, color.b, 1.0) + updateColor() + } + function setRed(r) { + _color.r = r; + updateColor() + } + function setGreen(g) { + _color.g = g; + updateColor() + } + function setBlue(b) { + _color.b = b; + updateColor() + } + + function updateColor() { + repaint() + newColor(_color) + } + function repaint() { + current.color = _color + } + + function resetSliders() { + redZone.set(_color.r) + greenZone.set(_color.g) + blueZone.set(_color.b) + } + + function setXColor(xcolor) { + setColor(Qt.rgba(xcolor.red/255, xcolor.green/255, color.blue/255, 1.0)) + } + function getXColor() { + return {red:_color.r * 255, green:_color.g * 255, blue:_color.b * 255} + } + + Rectangle { + id: current + anchors.fill: root + color: root._color; + } + Rectangle { + id: sliderBack + height: root.sliderHeight + anchors.bottom: root.bottom + anchors.left: root.left + anchors.right: root.right + color: Qt.rgba(0.2, 0.2, 0.2, 1) + opacity: root.hoveredOn * 0.5 + } + + MouseArea { + id: all + anchors.fill: root + hoverEnabled: true + onEntered: { + root.hoveredOn = 1.0; + resetSliders(); + } + onExited: { + root.hoveredOn = 0.0; + } + } + + Component.onCompleted: { + } + + Item { + id: redZone + anchors.top: root.top + anchors.bottom: root.bottom + anchors.left: root.left + width: root.zoneWidth + + function update(r) { + if (r < 0.0) { + r = 0.0 + } else if (r > 1.0) { + r = 1.0 + } + root.setRed(r) + set(r) + } + function set(r) { + redRect.width = r * redZone.width + redRect.color = Qt.rgba(r, 0, 0, 1) + } + + Rectangle { + id: redRect + anchors.bottom: parent.bottom + anchors.left: parent.left + height: root.sliderHeight + opacity: root.hoveredOn + } + + MouseArea { + id: redArea + anchors.fill: parent + onPositionChanged: { + redZone.update(mouse.x / redArea.width) + } + } + } + Item { + id: greenZone + anchors.top: root.top + anchors.bottom: root.bottom + anchors.horizontalCenter: root.horizontalCenter + width: root.zoneWidth + + function update(g) { + if (g < 0.0) { + g = 0.0 + } else if (g > 1.0) { + g = 1.0 + } + root.setGreen(g) + set(g) + } + function set(g) { + greenRect.width = g * greenZone.width + greenRect.color = Qt.rgba(0, g, 0, 1) + } + + Rectangle { + id: greenRect + anchors.bottom: parent.bottom + anchors.left: parent.left + height: root.sliderHeight + opacity: root.hoveredOn + } + + MouseArea { + id: greenArea + anchors.fill: parent + onPositionChanged: { + greenZone.update(mouse.x / greenArea.width) + } + } + } + Item { + id: blueZone + anchors.top: root.top + anchors.bottom: root.bottom + anchors.right: root.right + width: root.zoneWidth + + function update(b) { + if (b < 0.0) { + b = 0.0 + } else if (b > 1.0) { + b = 1.0 + } + root.setBlue(b) + set(b) + } + function set(b) { + blueRect.width = b * blueZone.width + blueRect.color = Qt.rgba(0, 0, b, 1) + } + + Rectangle { + id: blueRect + anchors.bottom: parent.bottom + anchors.left: parent.left + height: root.sliderHeight + opacity: root.hoveredOn + } + + MouseArea { + id: blueArea + anchors.fill: parent + onPositionChanged: { + blueZone.update(mouse.x / blueArea.width) + } + } + } +} diff --git a/scripts/developer/utilities/lib/plotperf/qmldir b/scripts/developer/utilities/lib/plotperf/qmldir index 5668f5034c..20b3fc9fcf 100644 --- a/scripts/developer/utilities/lib/plotperf/qmldir +++ b/scripts/developer/utilities/lib/plotperf/qmldir @@ -1 +1,2 @@ -PlotPerf 1.0 PlotPerf.qml \ No newline at end of file +PlotPerf 1.0 PlotPerf.qml +Color 1.0 Color.qml diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 830dc6de23..87e0e51726 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -30,6 +30,8 @@ Item { property alias min: sliderControl.minimumValue property alias max: sliderControl.maximumValue + signal valueChanged(real value) + Component.onCompleted: { // Binding favors qml value, so set it first sliderControl.value = root.config[root.property]; @@ -69,5 +71,7 @@ Item { anchors.rightMargin: 0 anchors.top: root.top anchors.topMargin: 0 + + onValueChanged: { root.valueChanged(value) } } } diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index 5175761978..e70565cec2 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -9,152 +9,162 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Set up the qml ui -var qml = Script.resolvePath('highlight.qml'); -var window = new OverlayWindow({ - title: 'Highlight', - source: qml, - width: 400, - height: 400, -}); -window.closed.connect(function() { Script.stop(); }); - "use strict"; -// Created by Sam Gondelman on 9/7/2017 -// Copyright 2017 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 +(function() { + var TABLET_BUTTON_NAME = "Highlight"; + var QMLAPP_URL = Script.resolvePath("./highlight.qml"); + var ICON_URL = Script.resolvePath("../../../system/assets/images/luci-i.svg"); + var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/luci-a.svg"); -(function() { // BEGIN LOCAL_SCOPE + + var onLuciScreen = false; -var END_DIMENSIONS = { - x: 0.15, - y: 0.15, - z: 0.15 -}; -var COLOR = {red: 97, green: 247, blue: 255}; -var end = { - type: "sphere", - dimensions: END_DIMENSIONS, - color: COLOR, - ignoreRayIntersection: true, - alpha: 1.0, - visible: true -} - -var COLOR2 = {red: 247, green: 97, blue: 255}; -var end2 = { - type: "sphere", - dimensions: END_DIMENSIONS, - color: COLOR2, - ignoreRayIntersection: true, - alpha: 1.0, - visible: true -} - -var highlightGroupIndex = 0 -var isSelectionAddEnabled = false -var isSelectionEnabled = false -var renderStates = [{name: "test", end: end}]; -var defaultRenderStates = [{name: "test", distance: 20.0, end: end2}]; -var time = 0 - -var ray = LaserPointers.createLaserPointer({ - joint: "Mouse", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_AVATARS | RayPick.PICK_INVISIBLE | RayPick.PICK_NONCOLLIDABLE, - renderStates: renderStates, - defaultRenderStates: defaultRenderStates, - enabled: false -}); - -function getSelectionName() { - var selectionName = "contextOverlayHighlightList" - - if (highlightGroupIndex>0) { - selectionName += highlightGroupIndex - } - return selectionName -} - -function fromQml(message) { - tokens = message.split(' ') - print("Received '"+message+"' from hightlight.qml") - if (tokens[0]=="highlight") { - highlightGroupIndex = parseInt(tokens[1]) - print("Switching to highlight group "+highlightGroupIndex) - } else if (tokens[0]=="pick") { - isSelectionEnabled = tokens[1]=='true' - print("Ray picking set to "+isSelectionEnabled.toString()) - if (isSelectionEnabled) { - LaserPointers.enableLaserPointer(ray) + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); } else { - LaserPointers.disableLaserPointer(ray) - } - time = 0 - } else if (tokens[0]=="add") { - isSelectionAddEnabled = tokens[1]=='true' - print("Add to selection set to "+isSelectionAddEnabled.toString()) - if (!isSelectionAddEnabled) { - Selection.clearSelectedItemsList(getSelectionName()) + tablet.loadQMLSource(QMLAPP_URL); } } -} -window.fromQml.connect(fromQml); + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL, + sortOrder: 1 + }); -function cleanup() { - LaserPointers.removeLaserPointer(ray); -} -Script.scriptEnding.connect(cleanup); + var hasEventBridge = false; -var prevID = 0 -var prevType = "" -var selectedID = 0 -var selectedType = "" -function update(deltaTime) { - - // you have to do this repeatedly because there's a bug but I'll fix it - LaserPointers.setRenderState(ray, "test"); - - var result = LaserPointers.getPrevRayPickResult(ray); - var selectionName = getSelectionName() - - if (isSelectionEnabled && result.type != RayPick.INTERSECTED_NONE) { - time += deltaTime - if (result.objectID != prevID) { - var typeName = "" - if (result.type == RayPick.INTERSECTED_ENTITY) { - typeName = "entity" - } else if (result.type == RayPick.INTERSECTED_OVERLAY) { - typeName = "overlay" - } else if (result.type == RayPick.INTERSECTED_AVATAR) { - typeName = "avatar" + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + // Create a Laser pointer used to pick and add objects to selections + var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 }; + var COLOR1 = {red: 255, green: 0, blue: 0}; + var COLOR2 = {red: 0, green: 255, blue: 0}; + var end1 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR1, + ignoreRayIntersection: true + } + var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignoreRayIntersection: true + } + var laser = Pointers.createPointer(PickType.Ray, { + joint: "Mouse", + filter: Picks.PICK_ENTITIES, + renderStates: [{name: "one", end: end1}], + defaultRenderStates: [{name: "one", end: end2, distance: 2.0}], + enabled: true + }); + Pointers.setRenderState(laser, "one"); + + var HoveringList = "Hovering" + var hoveringStyle = { + isOutlineSmooth: true, + outlineWidth: 5, + outlineUnoccludedColor: {red: 255, green: 128, blue: 128}, + outlineUnoccludedAlpha: 0.88, + outlineOccludedColor: {red: 255, green: 128, blue: 128}, + outlineOccludedAlpha:0.5, + fillUnoccludedColor: {red: 26, green: 0, blue: 0}, + fillUnoccludedAlpha: 0.0, + fillOccludedColor: {red: 26, green: 0, blue: 0}, + fillOccludedAlpha: 0.0 + } + Selection.enableListHighlight(HoveringList, hoveringStyle) + + var currentSelectionName = "" + var isSelectionEnabled = false + Pointers.disablePointer(laser) + + function fromQml(message) { + tokens = message.split(' ') + print("Received '"+message+"' from hightlight.qml") + if (tokens[0]=="highlight") { + currentSelectionName = tokens[1]; + print("Switching to highlight name "+currentSelectionName) + } else if (tokens[0]=="pick") { + isSelectionEnabled = tokens[1]=='true' + print("Ray picking set to "+isSelectionEnabled.toString()) + if (isSelectionEnabled) { + Pointers.enablePointer(laser) + } else { + Pointers.disablePointer(laser) + Selection.clearSelectedItemsList(HoveringList) } - - prevID = result.objectID; - prevType = typeName; time = 0 - } else if (time>1.0 && prevID!=selectedID) { - if (prevID != 0 && !isSelectionAddEnabled) { - Selection.removeFromSelectedItemsList(selectionName, selectedType, selectedID) - } - selectedID = prevID - selectedType = prevType - Selection.addToSelectedItemsList(selectionName, selectedType, selectedID) - print("HIGHLIGHT " + highlightGroupIndex + " picked type: " + result.type + ", id: " + result.objectID); } - } else { - if (prevID != 0 && !isSelectionAddEnabled) { - Selection.removeFromSelectedItemsList(selectionName, prevType, prevID) - } - prevID = 0 - selectedID = 0 - time = 0 } -} + + Entities.hoverEnterEntity.connect(function (id, event) { + // print("hoverEnterEntity"); + if (isSelectionEnabled) Selection.addToSelectedItemsList(HoveringList, "entity", id) + }) + + Entities.hoverOverEntity.connect(function (id, event) { + // print("hoverOverEntity"); + }) + + + Entities.hoverLeaveEntity.connect(function (id, event) { + if (isSelectionEnabled) Selection.removeFromSelectedItemsList(HoveringList, "entity", id) + // print("hoverLeaveEntity"); + }) + + function cleanup() { + Pointers.removePointer(ray); + Selection.disableListHighlight(HoveringList) + Selection.removeListFromMap(HoveringList) + + } + Script.scriptEnding.connect(cleanup); + +}()); -Script.update.connect(update); -}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml index 6be74fcf40..88d6a807ae 100644 --- a/scripts/developer/utilities/render/highlight.qml +++ b/scripts/developer/utilities/render/highlight.qml @@ -15,162 +15,184 @@ import QtQuick.Layouts 1.3 import "qrc:///qml/styles-uit" import "qrc:///qml/controls-uit" as HifiControls import "configSlider" +import "../lib/plotperf" +import "highlight" -Rectangle { +Item { id: root HifiConstants { id: hifi;} - color: hifi.colors.baseGray; - anchors.margins: hifi.dimensions.contentMargin.x - - property var debugConfig: Render.getConfig("RenderMainView.HighlightDebug") - property var highlightConfig: Render.getConfig("UpdateScene.HighlightStageSetup") + anchors.margins: 0 + property var listName: "contextOverlayHighlightList" + property var styleList: Selection.getHighlightedListNames() + signal sendToScript(var message); + Component.onCompleted: { + // Connect the signal from Selection when any selection content change and use it to refresh the current selection view + Selection.selectedItemsListChanged.connect(resetSelectionView) + } + + function resetSelectionView() { + if (selectionView !== undefined) { + selectionView.resetSelectionView(); + } + } + Column { id: col - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right + spacing: 5 + anchors.fill: root anchors.margins: hifi.dimensions.contentMargin.x Row { + id: controlbar spacing: 10 anchors.left: parent.left anchors.right: parent.right + height: 24 - HifiControls.CheckBox { + HifiControls.Button { id: debug - text: "View Mask" - checked: root.debugConfig["viewMask"] - onCheckedChanged: { - root.debugConfig["viewMask"] = checked; + text: "Refresh" + height: 24 + width: 82 + onClicked: { + print("list of highlight styles") + root.styleList = Selection.getHighlightedListNames() + + print(root.styleList) + styleSelectorLoader.sourceComponent = undefined; + styleSelectorLoader.sourceComponent = selectorWidget; } } - HifiControls.CheckBox { - text: "Hover select" - checked: false - onCheckedChanged: { - sendToScript("pick "+checked.toString()) - } - } - HifiControls.CheckBox { - text: "Add to selection" - checked: false - onCheckedChanged: { - sendToScript("add "+checked.toString()) + + Loader { + id: styleSelectorLoader + sourceComponent: selectorWidget + width: 300 + //anchors.right: parent.right + } + Component { + id: selectorWidget + HifiControls.ComboBox { + id: box + z: 999 + editable: true + colorScheme: hifi.colorSchemes.dark + model: root.styleList + label: "" + + Timer { + id: postpone + interval: 100; running: false; repeat: false + onTriggered: { + styleWidgetLoader.sourceComponent = styleWidget + resetSelectionView(); + } + } + onCurrentIndexChanged: { + root.listName = model[currentIndex]; + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 100ms later + styleWidgetLoader.sourceComponent = undefined; + postpone.interval = 100 + postpone.start() + } } } } - HifiControls.ComboBox { - id: box - width: 350 - z: 999 - editable: true - colorScheme: hifi.colorSchemes.dark - model: [ - "contextOverlayHighlightList", - "highlightList1", - "highlightList2", - "highlightList3", - "highlightList4"] - label: "" - - Timer { - id: postpone - interval: 100; running: false; repeat: false - onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets } - } - onCurrentIndexChanged: { - root.highlightConfig["selectionName"] = model[currentIndex]; - sendToScript("highlight "+currentIndex) - // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component - // by setting the loader source to Null and then recreate it 100ms later - paramWidgetLoader.sourceComponent = undefined; - postpone.interval = 100 - postpone.start() - } - } - + Separator {} Loader { - id: paramWidgetLoader - sourceComponent: paramWidgets - width: 350 + id: styleWidgetLoader + sourceComponent: styleWidget + anchors.left: parent.left + anchors.right: parent.right + height: 240 + } + + Separator {} + + HifiControls.CheckBox { + text: "Highlight Hovered" + checked: false + onCheckedChanged: { + if (checked) { + root.sendToScript("pick true") + } else { + root.sendToScript("pick false") + } + } } + Separator {} + Rectangle { + id: selectionView + anchors.left: parent.left + anchors.right: parent.right + height: 250 + color: hifi.colors.lightGray - Component { - id: paramWidgets - - Column { - spacing: 10 - anchors.margins: hifi.dimensions.contentMargin.x - - HifiControls.Label { - text: "Outline" - } - Column { - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right - HifiControls.CheckBox { - text: "Smooth" - checked: root.highlightConfig["isOutlineSmooth"] - onCheckedChanged: { - root.highlightConfig["isOutlineSmooth"] = checked; - } - } - Repeater { - model: ["Width:outlineWidth:5.0:0.0", - "Intensity:outlineIntensity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.highlightConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - } + function resetSelectionView() { + myModel.clear() + var entities = Selection.getSelectedItemsList(root.listName)["entities"] + if (entities !== undefined) { + myModel.append({ "objectID": "Entities" }) + for (var i = 0; i < entities.length; i++) { + myModel.append({ "objectID": JSON.stringify(entities[i]) }) } } - - Separator {} - HifiControls.Label { - text: "Color" - } - Repeater { - model: ["Red:colorR:1.0:0.0", - "Green:colorG:1.0:0.0", - "Blue:colorB:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.highlightConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] + var overlays = Selection.getSelectedItemsList(root.listName)["overlays"] + if (overlays !== undefined) { + myModel.append({ "objectID": "Overlays" }) + for (var i = 0; i < overlays.length; i++) { + myModel.append({ "objectID": JSON.stringify(overlays[i]) }) } } - - Separator {} - HifiControls.Label { - text: "Fill Opacity" - } - Repeater { - model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", - "Occluded:occludedFillOpacity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.highlightConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] + var avatars = Selection.getSelectedItemsList(root.listName)["avatars"] + if (avatars !== undefined) { + myModel.append({ "objectID": "Avatars" }) + for (var i = 0; i < avatars.length; i++) { + myModel.append({ "objectID": JSON.stringify(avatars[i]) }) } } + } + + ListModel { + id: myModel + } + + Component { + id: myDelegate + Row { + id: fruit + Text { text: objectID } + } + } + + ListView { + id: selectionListView + anchors.fill: parent + anchors.topMargin: 30 + model: myModel + delegate: myDelegate + } + } + } + + Component { + id: styleWidget + + HighlightStyle { + id: highlightStyle + anchors.left: parent.left + anchors.right: parent.right + highlightStyle: Selection.getListHighlightStyle(root.listName) + + onNewStyle: { + var style = getStyle() + // print("new style " + JSON.stringify(style) ) + Selection.enableListHighlight(root.listName, style) } } } diff --git a/scripts/developer/utilities/render/highlight/HighlightStyle.qml b/scripts/developer/utilities/render/highlight/HighlightStyle.qml new file mode 100644 index 0000000000..371b7e81f7 --- /dev/null +++ b/scripts/developer/utilities/render/highlight/HighlightStyle.qml @@ -0,0 +1,105 @@ +// +// highlightStyle.qml +// +// Created by Sam Gateau 12/4/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import "../configSlider" +import "../../lib/plotperf" +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + +Item { + id: root + property var highlightStyle + height: 48 + + anchors.margins: 0 + + signal newStyle() + + function getStyle() { + return highlightStyle; + } + + Component.onCompleted: { + } + + Column { + spacing: 5 + anchors.left: root.left + anchors.right: root.right + anchors.margins: 0 + + + + ConfigSlider { + label: "Outline Width" + integral: false + config: root.highlightStyle + property: "outlineWidth" + max: 10 + min: 0 + + anchors.left: parent.left + anchors.right: parent.right + + onValueChanged: { root.highlightStyle["outlineWidth"] = value; newStyle() } + } + HifiControls.CheckBox { + id: isOutlineSmooth + text: "Smooth Outline" + checked: root.highlightStyle["isOutlineSmooth"] + onCheckedChanged: { + root.highlightStyle["isOutlineSmooth"] = checked; + newStyle(); + } + } + + Repeater { + model: [ + "Outline Unoccluded:outlineUnoccludedColor:outlineUnoccludedAlpha", + "Outline Occluded:outlineOccludedColor:outlineOccludedAlpha", + "Fill Unoccluded:fillUnoccludedColor:fillUnoccludedAlpha", + "Fill Occluded:fillOccludedColor:fillOccludedAlpha"] + Column { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 0 + + Color { + height: 20 + anchors.right: parent.right + width: root.width / 2 + _color: Qt.rgba(root.highlightStyle[modelData.split(":")[1]].red / 255, root.highlightStyle[modelData.split(":")[1]].green / 255, root.highlightStyle[modelData.split(":")[1]].blue / 255, 1.0) + onNewColor: { + root.highlightStyle[modelData.split(":")[1]] = getXColor() + newStyle() + } + } + + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightStyle + property: modelData.split(":")[2] + max: 1.0 + min: 0.0 + + anchors.left: parent.left + anchors.right: parent.right + + onValueChanged: { root.highlightStyle[modelData.split(":")[2]] = value; newStyle() } + } + + } + } + + } +} diff --git a/scripts/developer/utilities/render/highlight/qmldir b/scripts/developer/utilities/render/highlight/qmldir new file mode 100644 index 0000000000..31fc576bbe --- /dev/null +++ b/scripts/developer/utilities/render/highlight/qmldir @@ -0,0 +1 @@ +HighlightStyle 1.0 HighlightStyle.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/highlightPage/HighlightPage.qml b/scripts/developer/utilities/render/highlightPage/HighlightPage.qml deleted file mode 100644 index 5669f90628..0000000000 --- a/scripts/developer/utilities/render/highlightPage/HighlightPage.qml +++ /dev/null @@ -1,116 +0,0 @@ -// -// highlightPage.qml -// developer/utilities/render -// -// Olivier Prat, created on 08/08/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// -import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.3 -import "../configSlider" -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls - -Rectangle { - id: root - property var highlightIndex: 0 - property var drawConfig: Render.getConfig("RenderMainView.HighlightEffect"+highlightIndex) - - HifiConstants { id: hifi;} - anchors.margins: hifi.dimensions.contentMargin.x - - Column { - spacing: 5 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: hifi.dimensions.contentMargin.x - - HifiControls.CheckBox { - id: glow - text: "Glow" - checked: root.drawConfig["glow"] - onCheckedChanged: { - root.drawConfig["glow"] = checked; - } - } - Repeater { - model: ["Width:width:5.0:0.0", - "Intensity:intensity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.drawConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - - anchors.left: parent.left - anchors.right: parent.right - } - } - - GroupBox { - title: "Color" - anchors.left: parent.left - anchors.right: parent.right - Column { - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: hifi.dimensions.contentMargin.x - - Repeater { - model: ["Red:colorR:1.0:0.0", - "Green:colorG:1.0:0.0", - "Blue:colorB:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.drawConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - - anchors.left: parent.left - anchors.right: parent.right - } - } - } - } - - GroupBox { - title: "Fill Opacity" - anchors.left: parent.left - anchors.right: parent.right - Column { - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: hifi.dimensions.contentMargin.x - - Repeater { - model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", - "Occluded:occludedFillOpacity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.drawConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - - anchors.left: parent.left - anchors.right: parent.right - } - } - } - } - } -} diff --git a/scripts/developer/utilities/render/highlightPage/qmldir b/scripts/developer/utilities/render/highlightPage/qmldir deleted file mode 100644 index bb3de24b84..0000000000 --- a/scripts/developer/utilities/render/highlightPage/qmldir +++ /dev/null @@ -1 +0,0 @@ -HighlightPage 1.0 HighlightPage.qml \ No newline at end of file diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index bad6793995..57f3b4fd8b 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -376,6 +376,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (PROFILE) { Script.endProfileRange("dispatch.run"); } + Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); }; this.setBlacklist = function() { @@ -470,7 +471,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.cleanup = function () { - Script.update.disconnect(_this.update); Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); Pointers.removePointer(this.mouseRayPick); @@ -501,5 +501,5 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); Script.scriptEnding.connect(controllerDispatcher.cleanup); - Script.update.connect(controllerDispatcher.update); + Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS); }()); diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 2c417a9dde..19d4417a12 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -242,7 +242,9 @@ GridTool = function(opts) { horizontalGrid.addListener(function(data) { webView.emitScriptEvent(JSON.stringify(data)); - selectionDisplay.updateHandles(); + if (selectionDisplay) { + selectionDisplay.updateHandles(); + } }); webView.webEventReceived.connect(function(data) { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index f0f3416819..004375bff7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -11,9 +11,12 @@ /* global Tablet, Script, HMD, UserActivityLogger, Entities */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +var selectionDisplay = null; // for gridTool.js to ignore + (function () { // BEGIN LOCAL_SCOPE - Script.include("../libraries/WebTablet.js"); + Script.include("/~/system/libraries/WebTablet.js"); + Script.include("/~/system/libraries/gridTool.js"); var METAVERSE_SERVER_URL = Account.metaverseServerURL; var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; @@ -170,6 +173,33 @@ })); } + var grid = new Grid(); + function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; + } + var HALF_TREE_SCALE = 16384; function getPositionToCreateEntity(extra) { var CREATE_DISTANCE = 2; @@ -258,10 +288,6 @@ } } } - - if (isActive) { - selectionManager.setSelections(pastedEntityIDs); - } } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); } diff --git a/server-console/package-lock.json b/server-console/package-lock.json new file mode 100644 index 0000000000..51b7f2c268 --- /dev/null +++ b/server-console/package-lock.json @@ -0,0 +1,2250 @@ +{ + "name": "HighFidelitySandbox", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz", + "integrity": "sha1-W2A1su6dT7XPhZ8Iqb6BsghJGEM=", + "dev": true + }, + "always-tail": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/always-tail/-/always-tail-0.2.0.tgz", + "integrity": "sha1-M5sa9E1QJQqgeg6H7Mw6JOxET/4=", + "requires": { + "debug": "0.7.4" + } + }, + "ansi-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz", + "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "ansicolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", + "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" + }, + "array-find-index": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz", + "integrity": "sha1-C8Jd2slB7IpJauJY/UrBiAA+868=", + "dev": true + }, + "asar": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/asar/-/asar-0.11.0.tgz", + "integrity": "sha1-uSbnksMV+MBIxDNx4yWwnJenZGQ=", + "dev": true, + "requires": { + "chromium-pickle-js": "0.1.0", + "commander": "2.9.0", + "cuint": "0.2.1", + "glob": "6.0.4", + "minimatch": "3.0.0", + "mkdirp": "0.5.1", + "mksnapshot": "0.3.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + } + } + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.3.2.tgz", + "integrity": "sha1-054L7kEs7Q6O2Uoj4xTzE6lbn9E=", + "requires": { + "lru-cache": "4.0.1" + } + }, + "balanced-match": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz", + "integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=" + }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dev": true, + "requires": { + "buffers": "0.1.1", + "chainsaw": "0.1.0" + } + }, + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "requires": { + "readable-stream": "2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "1.0.0", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "bluebird": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz", + "integrity": "sha1-AkpVFylTCIV/FPkfEQb8O1VfRGs=", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", + "integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=", + "requires": { + "balanced-match": "0.3.0", + "concat-map": "0.0.1" + } + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + } + }, + "cardinal": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.5.0.tgz", + "integrity": "sha1-ANX2YdvUqr/ffUHOSKWlm8o1opE=", + "requires": { + "ansicolors": "0.2.1", + "redeyed": "0.5.0" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "requires": { + "traverse": "0.3.9" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cheerio": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", + "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "requires": { + "css-select": "1.0.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.8.3", + "lodash": "3.10.1" + } + }, + "chromium-pickle-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.1.0.tgz", + "integrity": "sha1-HUixB9ghJqLz4hHC6iX4A7pVGyE=", + "dev": true + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "requires": { + "colors": "1.0.3" + } + }, + "cli-usage": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.2.tgz", + "integrity": "sha1-SXwg6vEuwneTk6m/rCJcX2y5FS0=", + "requires": { + "marked": "0.3.5", + "marked-terminal": "1.6.1", + "minimist": "0.2.0" + }, + "dependencies": { + "minimist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=" + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.1", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.0.0" + } + }, + "code-point-at": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", + "integrity": "sha1-9psZLT99keOC5Lcb3bd4eGGasMY=", + "requires": { + "number-is-nan": "1.0.0" + } + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=", + "dev": true, + "requires": { + "inherits": "2.0.1", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "1.0.0", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "css-select": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "requires": { + "boolbase": "1.0.0", + "css-what": "1.0.0", + "domutils": "1.4.3", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true + }, + "cuint": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.1.tgz", + "integrity": "sha1-VlBFzoEnxwxr80D1kcAEin1M/rw=", + "dev": true + }, + "dashdash": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz", + "integrity": "sha1-parm/Z2OFWYk6w3ZJZ6xK6JFOFo=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decompress-zip": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.0.tgz", + "integrity": "sha1-rjvLfjTGWHmt/nfhnDD4ZgK0vbA=", + "dev": true, + "requires": { + "binary": "0.3.0", + "graceful-fs": "4.1.3", + "mkpath": "0.1.0", + "nopt": "3.0.6", + "q": "1.4.1", + "readable-stream": "1.1.14", + "touch": "0.0.3" + } + }, + "deep-extend": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz", + "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "electron-download": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-2.1.1.tgz", + "integrity": "sha1-AH07HyrTco0nzP5PhJayY/kTijE=", + "dev": true, + "requires": { + "debug": "2.2.0", + "home-path": "1.0.3", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "mv": "2.1.1", + "nugget": "1.6.2", + "path-exists": "1.0.0", + "rc": "1.1.6" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + } + } + }, + "electron-log": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-1.1.1.tgz", + "integrity": "sha1-DboCXtM9DkW/j0DG6b487i+YbCg=" + }, + "electron-osx-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.3.0.tgz", + "integrity": "sha1-SXIB38g1OMVLNPGkBexuIaAf904=", + "dev": true, + "requires": { + "minimist": "1.2.0", + "run-series": "1.1.4" + } + }, + "electron-packager": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-6.0.2.tgz", + "integrity": "sha1-juAGaf6KNjCVAnMvz+0RfX7prCk=", + "dev": true, + "requires": { + "asar": "0.11.0", + "electron-download": "2.1.1", + "electron-osx-sign": "0.3.0", + "extract-zip": "1.5.0", + "fs-extra": "0.26.7", + "get-package-info": "0.0.2", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "mv": "2.1.1", + "ncp": "2.0.0", + "object-assign": "4.0.1", + "plist": "1.2.0", + "rcedit": "0.5.0", + "resolve": "1.1.7", + "rimraf": "2.5.2", + "run-series": "1.1.4" + } + }, + "electron-prebuilt": { + "version": "0.37.5", + "resolved": "https://registry.npmjs.org/electron-prebuilt/-/electron-prebuilt-0.37.5.tgz", + "integrity": "sha1-OkGJgod4FdOnrB+bLi9KcPQg/3A=", + "dev": true, + "requires": { + "electron-download": "2.1.1", + "extract-zip": "1.5.0" + } + }, + "end-of-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", + "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=", + "requires": { + "once": "1.3.3" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "error-ex": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz", + "integrity": "sha1-5ntD8+gsluo6WE/+4Ln8MyXYAtk=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima-fb": { + "version": "12001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-12001.1.0-dev-harmony-fb.tgz", + "integrity": "sha1-2EQAOEupXOJnjGF60kp/QICNqRU=" + }, + "extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=" + }, + "extract-zip": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", + "integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=", + "dev": true, + "requires": { + "concat-stream": "1.5.0", + "debug": "0.7.4", + "mkdirp": "0.5.0", + "yauzl": "2.4.1" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "1.0.0-rc4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", + "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", + "requires": { + "async": "1.5.2", + "combined-stream": "1.0.5", + "mime-types": "2.1.10" + } + }, + "fs-extra": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", + "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "2.2.3", + "klaw": "1.1.3", + "path-is-absolute": "1.0.0", + "rimraf": "2.5.2" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "requires": { + "is-property": "1.0.2" + } + }, + "get-package-info": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-0.0.2.tgz", + "integrity": "sha1-csOPvuLnZyhCSgDcFOJN0aKMI5E=", + "dev": true, + "requires": { + "bluebird": "3.3.5", + "lodash.get": "4.2.1", + "resolve": "1.1.7" + }, + "dependencies": { + "bluebird": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.3.5.tgz", + "integrity": "sha1-XudH8ce9lnZYtoOTZDCu51OVWjQ=", + "dev": true + } + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "glob": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz", + "integrity": "sha1-CqI1kxpKlqwT1g/6wvuHe9btT1g=", + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha1-kgM84RETxB4mKNYf36QLwQ3AFVw=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1", + "pinkie-promise": "2.0.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "2.0.0" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "home-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.3.tgz", + "integrity": "sha1-ns5Z/sPwMubRC1Q0/uJk30wt4y8=", + "dev": true + }, + "hosted-git-info": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz", + "integrity": "sha1-2elTsmmIvogJbEbpJklNlgTDAPg=", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.3.0", + "domutils": "1.5.1", + "entities": "1.0.0", + "readable-stream": "1.1.14" + }, + "dependencies": { + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + } + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.2.2", + "sshpk": "1.7.4" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "2.0.1" + }, + "dependencies": { + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.1" + } + } + } + }, + "inflight": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", + "integrity": "sha1-bLtFIevVHODsCpNr/XZX736bFyo=", + "requires": { + "once": "1.3.3", + "wrappy": "1.0.1" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-absolute": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", + "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", + "requires": { + "is-relative": "0.1.3" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-finite": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", + "integrity": "sha1-ZDhgPq6+J5OUj/SkJi7I2z1iWXs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.0" + } + }, + "is-my-json-valid": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz", + "integrity": "sha1-1Vd4qC/rawlj/0vhEdXRaE6JBwc=", + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "2.0.0", + "xtend": "4.0.1" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-relative": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", + "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", + "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jodid25519": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jsbn": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz", + "integrity": "sha1-ZQmH2g3XT06/WhE3eiqi0nPpff0=", + "optional": true + }, + "json-schema": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz", + "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.2.3.tgz", + "integrity": "sha1-4lK5mmr5AdPsQfMyWJyQUJp7xgU=" + }, + "jsonpointer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz", + "integrity": "sha1-OvHdIP6FRjkQ1GmjheMwF9KgMNk=" + }, + "jsprim": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz", + "integrity": "sha1-8gyQaskqvVjjt5rIvHCkiDJRLaE=", + "requires": { + "extsprintf": "1.0.2", + "json-schema": "0.2.2", + "verror": "1.3.6" + } + }, + "klaw": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.1.3.tgz", + "integrity": "sha1-faM8a0L5s9yc7ADRfxOvAX/MJyE=" + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "lodash._arraycopy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", + "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=" + }, + "lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._baseclone": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", + "requires": { + "lodash._arraycopy": "3.0.0", + "lodash._arrayeach": "3.0.0", + "lodash._baseassign": "3.2.0", + "lodash._basefor": "3.0.3", + "lodash.isarray": "3.0.4", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._basefor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", + "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=" + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "requires": { + "lodash._bindcallback": "3.0.1", + "lodash._isiterateecall": "3.0.9", + "lodash.restparam": "3.6.1" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._stringtopath": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.7.1.tgz", + "integrity": "sha1-1GYKFaWZeYj6WTAaO/2gXJpt494=", + "dev": true + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._createassigner": "3.1.1", + "lodash.keys": "3.1.2" + } + }, + "lodash.clonedeep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", + "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", + "requires": { + "lodash._baseclone": "3.3.0", + "lodash._bindcallback": "3.0.1" + } + }, + "lodash.get": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.2.1.tgz", + "integrity": "sha1-bpr8h7imwCFgZnpw9sjOEHiusRs=", + "dev": true, + "requires": { + "lodash._stringtopath": "4.7.1" + } + }, + "lodash.isarguments": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.8.tgz", + "integrity": "sha1-W/jaiH8B8qnknAoXXNrrMYoOQ9w=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.0.8", + "lodash.isarray": "3.0.4" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "loud-rejection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.3.0.tgz", + "integrity": "sha1-8omjkvF9K6rPGU0KZzAEOUQzsRU=", + "dev": true, + "requires": { + "array-find-index": "1.0.1", + "signal-exit": "2.1.2" + } + }, + "lru-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", + "integrity": "sha1-E0OVXtry432bnn7nJB4nxLn7cr4=", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "marked": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.5.tgz", + "integrity": "sha1-QROhWsXXvKFYpargciRYe5+hW5Q=" + }, + "marked-terminal": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.6.1.tgz", + "integrity": "sha1-BM0cfIsO9I2z9oAQ1zpXqWYcbM8=", + "requires": { + "cardinal": "0.5.0", + "chalk": "1.1.3", + "cli-table": "0.3.1", + "lodash.assign": "3.2.0", + "node-emoji": "0.1.0" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.3.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.3.5", + "object-assign": "4.0.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + } + }, + "mime-db": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz", + "integrity": "sha1-qyOmNy3J2G09yRIb0OvTgQWhkEo=" + }, + "mime-types": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz", + "integrity": "sha1-uTx8tDYuFtQQcqflRTj7TUMHCDc=", + "requires": { + "mime-db": "1.22.0" + } + }, + "minimatch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", + "integrity": "sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=", + "requires": { + "brace-expansion": "1.1.3" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mkpath": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz", + "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE=", + "dev": true + }, + "mksnapshot": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.0.tgz", + "integrity": "sha1-MuqYStb1MjJMaj+uZACHa4WChAc=", + "dev": true, + "requires": { + "decompress-zip": "0.3.0", + "fs-extra": "0.26.7", + "request": "2.55.0" + }, + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "dev": true, + "requires": { + "readable-stream": "1.0.34" + } + }, + "caseless": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz", + "integrity": "sha1-t7Zc5r8UE4hlOc/VM/CzDv+pz4g=", + "dev": true + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true + }, + "form-data": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=", + "dev": true, + "requires": { + "async": "0.9.2", + "combined-stream": "0.0.7", + "mime-types": "2.0.14" + } + }, + "har-validator": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", + "integrity": "sha1-2DhCsOtMQ1lgrrEIoGejqpTA7rI=", + "dev": true, + "requires": { + "bluebird": "2.10.2", + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1" + } + }, + "hawk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", + "integrity": "sha1-HnMc45RH+h0PbXB/e87r7A/R7B8=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "0.1.5", + "ctype": "0.5.3" + } + }, + "mime-db": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", + "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=", + "dev": true + }, + "mime-types": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", + "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", + "dev": true, + "requires": { + "mime-db": "1.12.0" + } + }, + "oauth-sign": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz", + "integrity": "sha1-fb6uRPbKRU4fFoRR1jB0ZzWBPOM=", + "dev": true + }, + "qs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz", + "integrity": "sha1-9854jld33wtQENp/fE5zujJHD1o=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "request": { + "version": "2.55.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", + "integrity": "sha1-11wc32eddrsQD5v/4f5VG1wk6T0=", + "dev": true, + "requires": { + "aws-sign2": "0.5.0", + "bl": "0.9.5", + "caseless": "0.9.0", + "combined-stream": "0.0.7", + "forever-agent": "0.6.1", + "form-data": "0.2.0", + "har-validator": "1.8.0", + "hawk": "2.3.1", + "http-signature": "0.10.1", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.0.14", + "node-uuid": "1.4.7", + "oauth-sign": "0.6.0", + "qs": "2.4.2", + "stringstream": "0.0.5", + "tough-cookie": "2.2.2", + "tunnel-agent": "0.4.2" + } + } + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "dev": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "dev": true, + "requires": { + "glob": "6.0.4" + } + } + } + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true + }, + "node-emoji": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-0.1.0.tgz", + "integrity": "sha1-P0QkpVuo7VDCVKE4WLJEVPQYJgI=" + }, + "node-notifier": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-4.5.0.tgz", + "integrity": "sha1-Ap7pjXqbxOlsnLUb6dTzYTI/6ps=", + "requires": { + "cli-usage": "0.1.2", + "growly": "1.3.0", + "lodash.clonedeep": "3.0.2", + "minimist": "1.2.0", + "semver": "5.1.0", + "shellwords": "0.1.0", + "which": "1.2.4" + } + }, + "node-uuid": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", + "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.0.7" + } + }, + "normalize-package-data": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz", + "integrity": "sha1-jZJPFClg4Xd+f/4XBUNjHMfLAt8=", + "dev": true, + "requires": { + "hosted-git-info": "2.1.4", + "is-builtin-module": "1.0.0", + "semver": "5.1.0", + "validate-npm-package-license": "3.0.1" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "1.0.0" + } + }, + "nugget": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz", + "integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=", + "dev": true, + "requires": { + "debug": "2.2.0", + "minimist": "1.2.0", + "pretty-bytes": "1.0.4", + "progress-stream": "1.2.0", + "request": "2.71.0", + "single-line-log": "0.4.1", + "throttleit": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + } + } + }, + "number-is-nan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz", + "integrity": "sha1-wCD1KcUoKt/dIz2R1LGBw9aG3Es=" + }, + "oauth-sign": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz", + "integrity": "sha1-GCQ5vbkTeL90YOdcZOpD5kSN7wY=" + }, + "object-assign": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz", + "integrity": "sha1-mVBEVsNZi1ytT8WcJuipuxB/4L0=", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "requires": { + "wrappy": "1.0.1" + } + }, + "os-homedir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz", + "integrity": "sha1-DWK99EuRb9O73PLKsZGUj7CU8Ac=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.0" + } + }, + "path-exists": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", + "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "2.0.4" + } + }, + "plist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", + "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "util-deprecate": "1.0.2", + "xmlbuilder": "4.0.0", + "xmldom": "0.1.22" + } + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "4.0.1", + "meow": "3.7.0" + } + }, + "process-nextick-args": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz", + "integrity": "sha1-D5awAc6pCxJZLOVm7bl+wR5pvQU=" + }, + "progress-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", + "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", + "dev": true, + "requires": { + "speedometer": "0.1.4", + "through2": "0.2.3" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pump": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.1.tgz", + "integrity": "sha1-8fFAn7m9EIW721drQ7hOxLXq3Bo=", + "requires": { + "end-of-stream": "1.1.0", + "once": "1.3.3" + } + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qs": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz", + "integrity": "sha1-7B0WJrJCeNmfD99FSeUk4k7O6yY=" + }, + "rc": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", + "integrity": "sha1-Q2UbdrauU7XIAvEVH6P8OwWZack=", + "dev": true, + "requires": { + "deep-extend": "0.4.1", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "1.0.4" + } + }, + "rcedit": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.5.0.tgz", + "integrity": "sha1-72a1p/AxB1IUGjTiLyPCJDPBzxU=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.3.5", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } + }, + "redeyed": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.5.0.tgz", + "integrity": "sha1-erAA5g7jh1rBFdKe2zLBQDxsJdE=", + "requires": { + "esprima-fb": "12001.1.0-dev-harmony-fb" + } + }, + "request": { + "version": "2.71.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.71.0.tgz", + "integrity": "sha1-bxRkPJxaZ8ruapXPjvBHfVYDvZE=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.3.2", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.0", + "forever-agent": "0.6.1", + "form-data": "1.0.0-rc4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.10", + "node-uuid": "1.4.7", + "oauth-sign": "0.8.1", + "qs": "6.1.0", + "stringstream": "0.0.5", + "tough-cookie": "2.2.2", + "tunnel-agent": "0.4.2" + } + }, + "request-progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-1.0.2.tgz", + "integrity": "sha1-XUBvCBMJ32G0qKqDzVc032Pxi/U=", + "requires": { + "throttleit": "1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "rimraf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", + "integrity": "sha1-YrqUf6TAtDY4Oa7+zU8PutYFlyY=", + "requires": { + "glob": "7.0.3" + } + }, + "run-series": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.4.tgz", + "integrity": "sha1-iac93F51ye+KtjIMChYA1qQRebk=", + "dev": true + }, + "semver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" + }, + "shellwords": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.0.tgz", + "integrity": "sha1-Zq/Ue2oSky2Qccv9mKUueFzQuhQ=" + }, + "signal-exit": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz", + "integrity": "sha1-N1h5sfkuvDszRIDQONxUam1VhWQ=", + "dev": true + }, + "single-line-log": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", + "integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.1" + } + }, + "spdx-exceptions": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz", + "integrity": "sha1-IguEI5EZrpBFqJLbgag/TOFvgP0=", + "dev": true + }, + "spdx-expression-parse": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz", + "integrity": "sha1-1SsUtelnB3FECvIlvLVjEirEUvY=", + "dev": true, + "requires": { + "spdx-exceptions": "1.0.4", + "spdx-license-ids": "1.2.1" + } + }, + "spdx-license-ids": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.1.tgz", + "integrity": "sha1-0H6hek0v2TUfnZTi/5zsdBgP6PM=", + "dev": true + }, + "speedometer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", + "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", + "dev": true + }, + "sshpk": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz", + "integrity": "sha1-rXtH3vymHIQV2WQkO2KwzmD7yjg=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "0.2.0", + "dashdash": "1.13.0", + "ecc-jsbn": "0.1.1", + "jodid25519": "1.0.2", + "jsbn": "0.1.0", + "tweetnacl": "0.14.3" + } + }, + "string-width": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz", + "integrity": "sha1-ySEptvHX9SrPmvQkom44ZKBc6wo=", + "requires": { + "code-point-at": "1.0.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "tar-fs": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", + "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", + "requires": { + "mkdirp": "0.5.1", + "pump": "1.0.1", + "tar-stream": "1.5.1" + } + }, + "tar-stream": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.1.tgz", + "integrity": "sha1-UWx00b6j4THMC5NIkpyag/CirRE=", + "requires": { + "bl": "1.1.2", + "end-of-stream": "1.1.0", + "readable-stream": "2.0.6", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "1.0.0", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dev": true, + "requires": { + "readable-stream": "1.1.14", + "xtend": "2.1.2" + }, + "dependencies": { + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "0.4.0" + } + } + } + }, + "touch": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", + "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", + "dev": true, + "requires": { + "nopt": "1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1.0.7" + } + } + } + }, + "tough-cookie": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" + }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz", + "integrity": "sha1-EQTj82rIcSXChycAZ9WC0YEzv+4=" + }, + "tweetnacl": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz", + "integrity": "sha1-PaOC9nDyXe1417PReSEZvKC3Ey0=", + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.2" + } + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "requires": { + "extsprintf": "1.0.2" + } + }, + "which": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz", + "integrity": "sha1-FVf5YIBgTlsRs1meufRbUKnv1yI=", + "requires": { + "is-absolute": "0.1.7", + "isexe": "1.1.2" + } + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, + "wrap-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz", + "integrity": "sha1-fTD4+HP5pbvDpk2ryNF34HGuQm8=", + "requires": { + "string-width": "1.0.1" + } + }, + "wrappy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz", + "integrity": "sha1-HmWWmWXMvC20VIxrhKbyxa7dRzk=" + }, + "xmlbuilder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz", + "integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=", + "dev": true, + "requires": { + "lodash": "3.10.1" + } + }, + "xmldom": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.22.tgz", + "integrity": "sha1-EN5OXpZJgfA8jMcvrcCNFLbDqiY=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yallist": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz", + "integrity": "sha1-MGxUODXwnuGkyyO3vOmrNByRzdQ=" + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "2.1.1", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "os-locale": "1.4.0", + "string-width": "1.0.1", + "window-size": "0.1.4", + "y18n": "3.2.1" + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "1.0.1" + } + } + } +} diff --git a/server-console/package.json b/server-console/package.json index f72ffc347f..8d2a177e0a 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -8,7 +8,6 @@ "" ], "devDependencies": { - "electron-compilers": "^1.0.1", "electron-packager": "^6.0.2", "electron-prebuilt": "0.37.5" }, diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index cf11ef9e7a..16446c5071 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -23,4 +23,8 @@ if (BUILD_TOOLS) add_subdirectory(oven) set_target_properties(oven PROPERTIES FOLDER "Tools") + + add_subdirectory(auto-tester) + set_target_properties(auto-tester PROPERTIES FOLDER "Tools") endif() + diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt new file mode 100644 index 0000000000..e5f2c1fb97 --- /dev/null +++ b/tools/auto-tester/CMakeLists.txt @@ -0,0 +1,57 @@ +set(TARGET_NAME auto-tester) +project(${TARGET_NAME}) + +# Automatically run UIC and MOC. This replaces the older WRAP macros +SET(CMAKE_AUTOUIC ON) +SET(CMAKE_AUTOMOC ON) + +setup_hifi_project(Core Widgets) +link_hifi_libraries() + +# FIX: Qt was built with -reduce-relocations +if (Qt5_POSITION_INDEPENDENT_CODE) + SET(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +# Qt includes +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${Qt5Core_INCLUDE_DIRS}) +include_directories(${Qt5Widgets_INCLUDE_DIRS}) + +set(QT_LIBRARIES Qt5::Core Qt5::Widgets) + +# Find all sources files +file (GLOB_RECURSE SOURCES src/*.cpp) +file (GLOB_RECURSE HEADERS src/*.h) +file (GLOB_RECURSE UIS src/ui/*.ui) + +if (WIN32) + # Do not show Console + set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) +endif() + +add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) + +target_link_libraries(PROJECT_NAME ${QT_LIBRARIES}) + +# Copy required dll's. +add_custom_command(TARGET auto-tester POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ +) + +if (WIN32) + find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) + + if (NOT WINDEPLOYQT_COMMAND) + message(FATAL_ERROR "Could not find windeployqt at ${QT_DIR}/bin. windeployqt is required.") + endif () + + # add a post-build command to call windeployqt to copy Qt plugins + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release> \"$\"" + ) +endif () \ No newline at end of file diff --git a/tools/auto-tester/ReadMe.md b/tools/auto-tester/ReadMe.md new file mode 100644 index 0000000000..57ec7ea623 --- /dev/null +++ b/tools/auto-tester/ReadMe.md @@ -0,0 +1,7 @@ +After building auto-tester, it needs to be deployed to Amazon SW + +* In folder hifi/build/tools/auto-tester + * Right click on the Release folder + * Select 7-Zip -> Add to archive + * Select Option ```Create SFX archive``` to create Release.exe +* Use Cyberduck (or any other AWS S3 client) to copy Release.exe to hifi-content/nissim/autoTester/ \ No newline at end of file diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp new file mode 100644 index 0000000000..121c98e16e --- /dev/null +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -0,0 +1,119 @@ +// +// ImageComparer.cpp +// +// Created by Nissim Hadar on 18 Nov 2017. +// Copyright 2013 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 "ImageComparer.h" + +#include + +// Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity +// The value is computed for the luminance component and the average value is returned +double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { + // Make sure the image is 8 bits per colour + QImage::Format format = expectedImage.format(); + if (format != QImage::Format::Format_RGB32) { + throw -1; + } + + const int L = 255; // (2^number of bits per pixel) - 1 + const double K1{ 0.01 }; + const double K2{ 0.03 }; + const double c1 = pow((K1 * L), 2); + const double c2 = pow((K2 * L), 2); + + // Coefficients for luminosity calculation + const double R_Y = 0.212655f; + const double G_Y = 0.715158f; + const double B_Y = 0.072187f; + + // First go over all full 8x8 blocks + // This is done in 3 loops + // 1) Read the pixels into a linear array (an optimization) + // 2) Calculate mean + // 3) Calculate variance and covariance + // + // p - pixel in expected image + // q - pixel in result image + // + const int WIN_SIZE = 8; + int x{ 0 }; // column index (start of block) + int y{ 0 }; // row index (start of block + + // Pixels are processed in square blocks + double p[WIN_SIZE * WIN_SIZE]; + double q[WIN_SIZE * WIN_SIZE]; + + int windowCounter{ 0 }; + double ssim{ 0.0 }; + while (x < expectedImage.width()) { + int lastX = x + WIN_SIZE - 1; + if (lastX > expectedImage.width() - 1) { + x -= (lastX - expectedImage.width()); + } + + while (y < expectedImage.height()) { + int lastY = y + WIN_SIZE - 1; + if (lastY > expectedImage.height() - 1) { + y -= (lastY - expectedImage.height()); + } + + // Collect pixels into linear arrays + int i{ 0 }; + for (int xx = 0; xx < WIN_SIZE; ++xx) { + for (int yy = 0; yy < WIN_SIZE; ++yy) { + // Get pixels + QRgb pixelP = expectedImage.pixel(QPoint(x + xx, y + yy)); + QRgb pixelQ = resultImage.pixel(QPoint(x + xx, y + yy)); + + // Convert to luminance + p[i] = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + q[i] = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); + + ++i; + } + } + + // Calculate mean + double mP{ 0.0 }; // average value of expected pixel + double mQ{ 0.0 }; // average value of result pixel + for (int j = 0; j < WIN_SIZE * WIN_SIZE; ++j) { + mP += p[j]; + mQ += q[j]; + } + mP /= (WIN_SIZE * WIN_SIZE); + mQ /= (WIN_SIZE * WIN_SIZE); + + // Calculate variance and covariance + double sigsqP{ 0.0 }; + double sigsqQ{ 0.0 }; + double sigPQ{ 0.0 }; + for (int j = 0; j < WIN_SIZE * WIN_SIZE; ++j) { + sigsqP += pow((p[j] - mP), 2); + sigsqQ += pow((q[j] - mQ), 2); + + sigPQ += (p[j] - mP) * (q[j] - mQ); + } + sigsqP /= (WIN_SIZE * WIN_SIZE); + sigsqQ /= (WIN_SIZE * WIN_SIZE); + sigPQ /= (WIN_SIZE * WIN_SIZE); + + double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2); + double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2); + + ssim += numerator / denominator; + ++windowCounter; + + y += WIN_SIZE; + } + + x += WIN_SIZE; + y = 0; + } + + return ssim / windowCounter; +}; diff --git a/tools/auto-tester/src/ImageComparer.h b/tools/auto-tester/src/ImageComparer.h new file mode 100644 index 0000000000..7b7b8b0b74 --- /dev/null +++ b/tools/auto-tester/src/ImageComparer.h @@ -0,0 +1,21 @@ +// +// ImageComparer.h +// +// Created by Nissim Hadar on 18 Nov 2017. +// Copyright 2013 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 +// +#ifndef hifi_ImageComparer_h +#define hifi_ImageComparer_h + +#include +#include + +class ImageComparer { +public: + double compareImages(QImage resultImage, QImage expectedImage) const; +}; + +#endif // hifi_ImageComparer_h diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp new file mode 100644 index 0000000000..8cb36fcfca --- /dev/null +++ b/tools/auto-tester/src/Test.cpp @@ -0,0 +1,383 @@ +// +// Test.cpp +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 "Test.h" + +#include +#include +#include + +Test::Test() { + snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); + + expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg"); + + mismatchWindow.setModal(true); +} + +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) { + // Loop over both lists and compare each pair of images + // Quit loop if user has aborted due to a failed test. + const double THRESHOLD{ 0.999 }; + bool success{ true }; + bool keepOn{ true }; + for (int i = 0; keepOn && i < expectedImages.length(); ++i) { + // First check that images are the same size + QImage resultImage(resultImages[i]); + QImage expectedImage(expectedImages[i]); + if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) { + messageBox.critical(0, "Internal error", "Images are not the same size"); + exit(-1); + } + + double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical + try { + similarityIndex = imageComparer.compareImages(resultImage, expectedImage); + } catch (...) { + messageBox.critical(0, "Internal error", "Image not in expected format"); + exit(-1); + } + + if (similarityIndex < THRESHOLD) { + mismatchWindow.setTestFailure(TestFailure{ + (float)similarityIndex, + expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image + }); + + mismatchWindow.exec(); + + switch (mismatchWindow.getUserResponse()) { + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + success = false; + break; + case USER_RESPONSE_ABORT: + keepOn = false; + success = false; + break; + default: + assert(false); + break; + } + } + } + + return success; +} + +void Test::evaluateTests() { + // Get list of JPEG images in folder, sorted by name + QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (pathToImageDirectory == "") { + return; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + + // Separate images into two lists. The first is the expected images, the second is the test results + // Images that are in the wrong format are ignored. + QStringList expectedImages; + QStringList resultImages; + foreach(QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; + if (isInExpectedImageFilenameFormat(currentFilename)) { + expectedImages << fullCurrentFilename; + } else if (isInSnapshotFilenameFormat(currentFilename)) { + resultImages << fullCurrentFilename; + } + } + + // The number of images in each list should be identical + if (expectedImages.length() != resultImages.length()) { + messageBox.critical(0, + "Test failed", + "Found " + QString::number(resultImages.length()) + " images in directory" + + "\nExpected to find " + QString::number(expectedImages.length()) + " images" + ); + + exit(-1); + } + + bool success = compareImageLists(expectedImages, resultImages); + + if (success) { + messageBox.information(0, "Success", "All images are as expected"); + } else { + messageBox.information(0, "Failure", "One or more images are not as expected"); + } +} + +// Two criteria are used to decide if a folder contains valid test results. +// 1) a 'test'js' file exists in the folder +// 2) the folder has the same number of actual and expected images +void Test::evaluateTestsRecursively() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + bool success{ true }; + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + if (directory[directory.length() - 1] == '.') { + // ignore '.', '..' directories + continue; + } + + // + const QString testPathname{ directory + "/" + testFilename }; + QFileInfo fileInfo(testPathname); + if (!fileInfo.exists()) { + // Folder does not contain 'test.js' + continue; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); + + // Separate images into two lists. The first is the expected images, the second is the test results + // Images that are in the wrong format are ignored. + QStringList expectedImages; + QStringList resultImages; + foreach(QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = directory + "/" + currentFilename; + if (isInExpectedImageFilenameFormat(currentFilename)) { + expectedImages << fullCurrentFilename; + } else if (isInSnapshotFilenameFormat(currentFilename)) { + resultImages << fullCurrentFilename; + } + } + + if (expectedImages.length() != resultImages.length()) { + // Number of images doesn't match + continue; + } + + // Set success to false if any test has failed + success &= compareImageLists(expectedImages, resultImages); + } + + if (success) { + messageBox.information(0, "Success", "All images are as expected"); + } else { + messageBox.information(0, "Failure", "One or more images are not as expected"); + } +} + +void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { + textStream << "var test" << testNumber << " = Script.require(\"" << "file:///" << testPathname + "\");" << endl; +} + +// Creates a single script in a user-selected folder. +// This script will run all text.js scripts in every applicable sub-folder +void Test::createRecursiveScript() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + QFile allTestsFilename(topLevelDirectory + "/" + "allTests.js"); + if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { + messageBox.critical(0, + "Internal Error", + "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""); + + exit(-1); + } + + QTextStream textStream(&allTestsFilename); + textStream << "// This is an automatically generated file, created by auto-tester" << endl; + + // The main will call each test after the previous test is completed + // This is implemented with an interval timer that periodically tests if a + // running test has increment a testNumber variable that it received as an input. + int testNumber = 1; + QVector testPathnames; + + // First test if top-level folder has a test.js file + const QString testPathname{ topLevelDirectory + "/" + testFilename }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + // Current folder contains a test + importTest(textStream, testPathname, testNumber); + ++testNumber; + + testPathnames << testPathname; + } + + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + if (directory[directory.length() - 1] == '.') { + // ignore '.', '..' directories + continue; + } + + const QString testPathname{ directory + "/" + testFilename }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + // Current folder contains a test + importTest(textStream, testPathname, testNumber); + ++testNumber; + + testPathnames << testPathname; + } + } + + if (testPathnames.length() <= 0) { + messageBox.information(0, "Failure", "No \"test.js\" files found"); + allTestsFilename.close(); + return; + } + + textStream << endl; + + // Define flags for each test + for (int i = 1; i <= testPathnames.length(); ++i) { + textStream << "var test" << i << "HasNotStarted = true;" << endl; + } + + // Leave a blank line in the main + textStream << endl; + + const int TEST_PERIOD = 1000; // in milliseconds + const QString tab = " "; + + textStream << "// Check every second if the current test is complete and the next test can be run" << endl; + textStream << "var testTimer = Script.setInterval(" << endl; + textStream << tab << "function() {" << endl; + + const QString testFunction = "test"; + for (int i = 1; i <= testPathnames.length(); ++i) { + // First test starts immediately, all other tests wait for the previous test to complete. + // The script produced will look as follows: + // if (test1HasNotStarted) { + // test1HasNotStarted = false; + // test1.test(); + // print("******started test 1******"); + // } + // | + // | + // if (test5.complete && test6HasNotStarted) { + // test6HasNotStarted = false; + // test7.test(); + // print("******started test 6******"); + // } + // | + // | + // if (test12.complete) { + // print("******stopping******"); + // Script.stop(); + // } + // + if (i == 1) { + textStream << tab << tab << "if (test1HasNotStarted) {" << endl; + } else { + textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl; + } + textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl; + textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl; + textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl; + + textStream << tab << tab << "}" << endl << endl; + + } + + // Add extra step to stop the script + textStream << tab << tab << "if (test" << testPathnames.length() << ".complete) {" << endl; + textStream << tab << tab << tab << "print(\"******stopping******\");" << endl; + textStream << tab << tab << tab << "Script.stop();" << endl; + textStream << tab << tab << "}" << endl << endl; + + textStream << tab << "}," << endl; + textStream << endl; + textStream << tab << TEST_PERIOD << endl; + textStream << ");" << endl << endl; + + textStream << "// Stop the timer and clear the module cache" << endl; + textStream << "Script.scriptEnding.connect(" << endl; + textStream << tab << "function() {" << endl; + textStream << tab << tab << "Script.clearInterval(testTimer);" << endl; + textStream << tab << tab << "Script.require.cache = {};" << endl; + textStream << tab << "}" << endl; + textStream << ");" << endl; + + allTestsFilename.close(); + + messageBox.information(0, "Success", "Script has been created"); +} + +void Test::createTest() { + // Rename files sequentially, as ExpectedResult_1.jpeg, ExpectedResult_2.jpg and so on + // Any existing expected result images will be deleted + QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (pathToImageDirectory == "") { + return; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + + int i = 1; + foreach (QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; + if (isInExpectedImageFilenameFormat(currentFilename)) { + if (!QFile::remove(fullCurrentFilename)) { + messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted"); + exit(-1); + } + } else if (isInSnapshotFilenameFormat(currentFilename)) { + const int MAX_IMAGES = 100000; + if (i >= MAX_IMAGES) { + messageBox.critical(0, "Error", "More than 100,000 images not supported"); + exit(-1); + } + QString newFilename = "ExpectedImage_" + QString::number(i-1).rightJustified(5, '0') + ".jpg"; + QString fullNewFileName = pathToImageDirectory + "/" + newFilename; + + if (!imageDirectory.rename(fullCurrentFilename, newFilename)) { + if (!QFile::exists(fullCurrentFilename)) { + messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" + + fullCurrentFilename + " not found" + + "\nTest creation aborted" + ); + exit(-1); + } else { + messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" + + "unknown error" + "\nTest creation aborted" + ); + exit(-1); + } + } + ++i; + } + } + + messageBox.information(0, "Success", "Test images have been created"); +} + +QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) { + imageDirectory = QDir(pathToImageDirectory); + QStringList nameFilters; + nameFilters << "*.jpg"; + + return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); +} + +bool Test::isInSnapshotFilenameFormat(QString filename) { + return (snapshotFilenameFormat.match(filename).hasMatch()); +} + +bool Test::isInExpectedImageFilenameFormat(QString filename) { + return (expectedImageFilenameFormat.match(filename).hasMatch()); +} \ No newline at end of file diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h new file mode 100644 index 0000000000..1f7b1e92a7 --- /dev/null +++ b/tools/auto-tester/src/Test.h @@ -0,0 +1,55 @@ +// +// Test.h +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 +// + +#ifndef hifi_test_h +#define hifi_test_h + +#include +#include +#include + +#include "ImageComparer.h" +#include "ui/MismatchWindow.h" + +class Test { +public: + Test(); + + void evaluateTests(); + void evaluateTestsRecursively(); + void createRecursiveScript(); + void createTest(); + + QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); + + bool isInSnapshotFilenameFormat(QString filename); + bool isInExpectedImageFilenameFormat(QString filename); + + void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); + +private: + const QString testFilename{ "test.js" }; + + QMessageBox messageBox; + + QDir imageDirectory; + + QRegularExpression snapshotFilenameFormat; + QRegularExpression expectedImageFilenameFormat; + + MismatchWindow mismatchWindow; + + ImageComparer imageComparer; + + bool compareImageLists(QStringList expectedImages, QStringList resultImages); +}; + +#endif // hifi_test_h diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h new file mode 100644 index 0000000000..126177358f --- /dev/null +++ b/tools/auto-tester/src/common.h @@ -0,0 +1,37 @@ +// +// common.h +// +// Created by Nissim Hadar on 10 Nov 2017. +// Copyright 2013 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 +// +#ifndef hifi_common_h +#define hifi_common_h + +#include + +class TestFailure { +public: + TestFailure(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) : + _error(error), + _pathname(pathname), + _expectedImageFilename(expectedImageFilename), + _actualImageFilename(actualImageFilename) + {} + + double _error; + QString _pathname; + QString _expectedImageFilename; + QString _actualImageFilename; +}; + +enum UserResponse { + USER_RESPONSE_INVALID, + USER_RESPONSE_PASS, + USE_RESPONSE_FAIL, + USER_RESPONSE_ABORT +}; + +#endif // hifi_common_h diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp new file mode 100644 index 0000000000..6e5e06b732 --- /dev/null +++ b/tools/auto-tester/src/main.cpp @@ -0,0 +1,20 @@ +// +// main.cpp +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 +#include "ui/AutoTester.h" + +int main(int argc, char *argv[]) { + QApplication application(argc, argv); + + AutoTester autoTester; + autoTester.show(); + + return application.exec(); +} diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp new file mode 100644 index 0000000000..105baddb92 --- /dev/null +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -0,0 +1,35 @@ +// +// AutoTester.cpp +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 "AutoTester.h" + +AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { + ui.setupUi(this); +} + +void AutoTester::on_evaluateTestsButton_clicked() { + test.evaluateTests(); +} + +void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { + test.evaluateTestsRecursively(); +} + +void AutoTester::on_createRecursiveScriptButton_clicked() { + test.createRecursiveScript(); +} + +void AutoTester::on_createTestButton_clicked() { + test.createTest(); +} + +void AutoTester::on_closeButton_clicked() { + exit(0); +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h new file mode 100644 index 0000000000..acfea32ba1 --- /dev/null +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -0,0 +1,37 @@ +// +// AutoTester.h +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 +// +#ifndef hifi_AutoTester_h +#define hifi_AutoTester_h + +#include +#include "ui_AutoTester.h" +#include "../Test.h" + +class AutoTester : public QMainWindow { + Q_OBJECT + +public: + AutoTester(QWidget *parent = Q_NULLPTR); + +private slots: +void on_evaluateTestsButton_clicked(); +void on_evaluateTestsRecursivelyButton_clicked(); +void on_createRecursiveScriptButton_clicked(); + void on_createTestButton_clicked(); + void on_closeButton_clicked(); + +private: + Ui::AutoTesterClass ui; + + Test test; +}; + +#endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui new file mode 100644 index 0000000000..7032ef9710 --- /dev/null +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -0,0 +1,106 @@ + + + AutoTesterClass + + + + 0 + 0 + 286 + 470 + + + + AutoTester + + + + + + 60 + 360 + 160 + 40 + + + + Close + + + + + + 60 + 270 + 160 + 40 + + + + Create Test + + + + + + 60 + 20 + 160 + 40 + + + + Evaluate Test + + + + + + 60 + 210 + 160 + 40 + + + + Create Recursive Script + + + + + + 60 + 75 + 160 + 40 + + + + Evaluate Tests Recursively + + + + + + + 0 + 0 + 286 + 21 + + + + + + TopToolBarArea + + + false + + + + + + + + diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp new file mode 100644 index 0000000000..07664a1667 --- /dev/null +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -0,0 +1,46 @@ +// +// MismatchWindow.cpp +// +// Created by Nissim Hadar on 9 Nov 2017. +// Copyright 2013 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 "MismatchWindow.h" + +#include + +MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { + setupUi(this); + + expectedImage->setScaledContents(true); + resultImage->setScaledContents(true); +} + +void MismatchWindow::setTestFailure(TestFailure testFailure) { + errorLabel->setText("Similarity: " + QString::number(testFailure._error)); + + imagePath->setText("Path to test: " + testFailure._pathname); + + expectedFilename->setText(testFailure._expectedImageFilename); + expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); + + resultFilename->setText(testFailure._actualImageFilename); + resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); +} + +void MismatchWindow::on_passTestButton_clicked() { + _userResponse = USER_RESPONSE_PASS; + close(); +} + +void MismatchWindow::on_failTestButton_clicked() { + _userResponse = USE_RESPONSE_FAIL; + close(); +} + +void MismatchWindow::on_abortTestsButton_clicked() { + _userResponse = USER_RESPONSE_ABORT; + close(); +} diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h new file mode 100644 index 0000000000..7c72b7b0b7 --- /dev/null +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -0,0 +1,38 @@ +// +// MismatchWindow.h +// +// Created by Nissim Hadar on 9 Nov 2017. +// Copyright 2013 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 +// +#ifndef hifi_MismatchWindow_h +#define hifi_MismatchWindow_h + +#include "ui_MismatchWindow.h" + +#include "../common.h" + +class MismatchWindow : public QDialog, public Ui::MismatchWindow +{ + Q_OBJECT + +public: + MismatchWindow(QWidget *parent = Q_NULLPTR); + + void setTestFailure(TestFailure testFailure); + + UserResponse getUserResponse() { return _userResponse; } + +private slots: + void on_passTestButton_clicked(); + void on_failTestButton_clicked(); + void on_abortTestsButton_clicked(); + +private: + UserResponse _userResponse{ USER_RESPONSE_INVALID }; +}; + + +#endif // hifi_MismatchWindow_h diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui new file mode 100644 index 0000000000..cab6c61e1c --- /dev/null +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -0,0 +1,157 @@ + + + MismatchWindow + + + + 0 + 0 + 1585 + 694 + + + + MismatchWindow + + + + + 20 + 170 + 720 + 362 + + + + expected image + + + + + + 760 + 170 + 720 + 362 + + + + result image + + + + + + 760 + 90 + 800 + 28 + + + + + 16 + + + + result image filename + + + + + + 40 + 90 + 700 + 28 + + + + + 16 + + + + expected image filename + + + + + + 40 + 30 + 1200 + 28 + + + + + 16 + + + + image path + + + + + + 30 + 600 + 75 + 23 + + + + Pass + + + + + + 330 + 600 + 75 + 23 + + + + Fail + + + + + + 630 + 600 + 75 + 23 + + + + Abort Tests + + + + + + 810 + 600 + 720 + 28 + + + + + 16 + + + + similarity + + + + + + + diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 1022c204c5..321f81ba8f 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -17,4 +17,4 @@ if (UNIX) endif() endif () -set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) +install_beside_console() diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 5ab995be95..5af65c4dc0 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "ModelBakingLoggingCategory.h" #include "Oven.h" @@ -22,22 +23,30 @@ BakerCLI::BakerCLI(Oven* parent) : QObject(parent) { } -void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { +void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type) { // if the URL doesn't have a scheme, assume it is a local file if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") { inputUrl.setScheme("file"); } - static const QString MODEL_EXTENSION { ".fbx" }; + qDebug() << "Baking file type: " << type; + + static const QString MODEL_EXTENSION { "fbx" }; + + QString extension = type; + + if (extension.isNull()) { + auto url = inputUrl.toDisplayString(); + extension = url.mid(url.lastIndexOf('.')); + } // check what kind of baker we should be creating - bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive); - bool isSupportedImage = false; + bool isFBX = extension == MODEL_EXTENSION; - for (QByteArray format : QImageReader::supportedImageFormats()) { - isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive); - } + bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1()); + + _outputPath = outputPath; // create our appropiate baker if (isFBX) { @@ -48,7 +57,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { _baker->moveToThread(qApp->getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; - QApplication::exit(1); + QApplication::exit(OVEN_STATUS_CODE_FAIL); } // invoke the bake method on the baker thread @@ -60,5 +69,17 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { void BakerCLI::handleFinishedBaker() { qCDebug(model_baking) << "Finished baking file."; - QApplication::exit(_baker.get()->hasErrors()); + int exitCode = OVEN_STATUS_CODE_SUCCESS; + // Do we need this? + if (_baker->wasAborted()) { + exitCode = OVEN_STATUS_CODE_ABORT; + } else if (_baker->hasErrors()) { + exitCode = OVEN_STATUS_CODE_FAIL; + QFile errorFile { _outputPath.absoluteFilePath(OVEN_ERROR_FILENAME) }; + if (errorFile.open(QFile::WriteOnly)) { + errorFile.write(_baker->getErrors().join('\n').toUtf8()); + errorFile.close(); + } + } + QApplication::exit(exitCode); } diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index cb2b908059..7d362eb898 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -13,22 +13,32 @@ #define hifi_BakerCLI_h #include +#include + +#include #include "Baker.h" #include "Oven.h" +static const int OVEN_STATUS_CODE_SUCCESS { 0 }; +static const int OVEN_STATUS_CODE_FAIL { 1 }; +static const int OVEN_STATUS_CODE_ABORT { 2 }; + +static const QString OVEN_ERROR_FILENAME = "errors.txt"; + class BakerCLI : public QObject { Q_OBJECT public: BakerCLI(Oven* parent); - void bakeFile(QUrl inputUrl, const QString outputPath); + void bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type = QString::null); private slots: void handleFinishedBaker(); private: + QDir _outputPath; std::unique_ptr _baker; }; -#endif // hifi_BakerCLI_h \ No newline at end of file +#endif // hifi_BakerCLI_h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 973eafa1f5..05c711177e 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -30,6 +30,7 @@ static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/exp static const QString CLI_INPUT_PARAMETER = "i"; static const QString CLI_OUTPUT_PARAMETER = "o"; +static const QString CLI_TYPE_PARAMETER = "t"; Oven::Oven(int argc, char* argv[]) : QApplication(argc, argv) @@ -51,7 +52,8 @@ Oven::Oven(int argc, char* argv[]) : parser.addOptions({ { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, - { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" } + { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }, + { CLI_TYPE_PARAMETER, "Type of asset.", "type" } }); parser.addHelpOption(); parser.process(*this); @@ -71,7 +73,8 @@ Oven::Oven(int argc, char* argv[]) : BakerCLI* cli = new BakerCLI(this); QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); - cli->bakeFile(inputUrl, outputUrl.toString()); + QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; + cli->bakeFile(inputUrl, outputUrl.toString(), type); } else { parser.showHelp(); QApplication::quit(); diff --git a/tools/oven/src/ui/BakeWidget.h b/tools/oven/src/ui/BakeWidget.h index 00996128ed..34cced537a 100644 --- a/tools/oven/src/ui/BakeWidget.h +++ b/tools/oven/src/ui/BakeWidget.h @@ -14,6 +14,8 @@ #include +#include + #include class BakeWidget : public QWidget {