3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 05:15:34 +02:00

Merge branch 'master' of github.com:highfidelity/hifi into feat/obj-baker-update

This commit is contained in:
Ryan Huffman 2017-12-11 13:39:58 -08:00
commit c2e0f19591
74 changed files with 5072 additions and 824 deletions

View file

@ -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
$<TARGET_FILE_DIR:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>)
else()
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink
$<TARGET_FILE:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>/oven)
endif()
if (WIN32)
package_libraries_for_deployment()
endif()

View file

@ -29,11 +29,10 @@
#include <QtCore/QUrlQuery>
#include <ClientServerUtils.h>
#include <FBXBaker.h>
#include <JSBaker.h>
#include <NodeType.h>
#include <SharedUtil.h>
#include <PathUtils.h>
#include <image/Image.h>
#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() {

View file

@ -11,11 +11,18 @@
#include "BakeAssetTask.h"
#include <QtCore/QThread>
#include <mutex>
#include <QtCore/QThread>
#include <QCoreApplication>
#include <FBXBaker.h>
#include <PathUtils.h>
#include <JSBaker.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 };
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>("QProcess::ProcessError");
qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
});
}
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
@ -41,67 +52,76 @@ void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
};
void BakeAssetTask::run() {
_isBaking.store(true);
qRegisterMetaType<QVector<QString> >("QVector<QString>");
TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); };
QString tempOutputDir;
if (_assetPath.endsWith(".fbx")) {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<FBXBaker> {
new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
};
} else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) {
_baker = std::unique_ptr<JSBaker>{
new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir())
};
} else {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<TextureBaker> {
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<void(QProcess::*)(int, QProcess::ExitStatus)>(&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<QString> 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<QString>::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();
}
}

View file

@ -17,9 +17,10 @@
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QRunnable>
#include <QDir>
#include <QProcess>
#include <AssetUtils.h>
#include <Baker.h>
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<QString> outputFiles);
@ -44,9 +44,8 @@ private:
AssetHash _assetHash;
AssetPath _assetPath;
QString _filePath;
std::unique_ptr<Baker> _baker;
std::unique_ptr<QProcess> _ovenProcess { nullptr };
std::atomic<bool> _wasAborted { false };
std::atomic<bool> _didFinish { false };
};
#endif // hifi_BakeAssetTask_h

View file

@ -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;

View file

@ -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) {

View file

@ -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;

View file

@ -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 <class T> 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 <class T> 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 <class T> 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<QVariant> avatarIDs;
QList<QVariant> entityIDs;
QList<QVariant> 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;
}

View file

@ -21,22 +21,23 @@
#include "RenderableEntityItem.h"
#include "ui/overlays/Overlay.h"
#include <avatar/AvatarManager.h>
#include <render/HighlightStyle.h>
class GameplayObjects {
public:
GameplayObjects();
bool getContainsData() { return containsData; }
bool getContainsData() const { return containsData; }
std::vector<QUuid> getAvatarIDs() { return _avatarIDs; }
std::vector<QUuid> getAvatarIDs() const { return _avatarIDs; }
bool addToGameplayObjects(const QUuid& avatarID);
bool removeFromGameplayObjects(const QUuid& avatarID);
std::vector<EntityItemID> getEntityIDs() { return _entityIDs; }
std::vector<EntityItemID> getEntityIDs() const { return _entityIDs; }
bool addToGameplayObjects(const EntityItemID& entityID);
bool removeFromGameplayObjects(const EntityItemID& entityID);
std::vector<OverlayID> getOverlayIDs() { return _overlayIDs; }
std::vector<OverlayID> 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<SelectionToSceneHandler>;
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<QString, GameplayObjects> _selectedItemsListMap;
mutable QReadWriteLock _selectionHandlersLock;
QMap<QString, SelectionToSceneHandler*> _handlerMap;
mutable QReadWriteLock _highlightStylesLock;
QMap<QString, SelectionHighlightStyle> _highlightStyleMap;
template <class T> bool addToGameplayObjects(const QString& listName, T idToAdd);
template <class T> 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

View file

@ -22,31 +22,3 @@ void WalletScriptingInterface::refreshWalletStatus() {
auto wallet = DependencyManager::get<Wallet>();
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<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
tablet->loadQMLSource(CHECKOUT_QML_PATH);
DependencyManager::get<HMDScriptingInterface>()->openTablet();
QQuickItem* root = nullptr;
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) {
root = DependencyManager::get<OffscreenUi>()->getRootItem();
} else {
root = tablet->getTabletRoot();
}
CheckoutProxy* checkout = new CheckoutProxy(root->findChild<QObject*>("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);
}

View file

@ -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();

View file

@ -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(); }

View file

@ -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<NodeList>();
@ -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

View file

@ -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;

View file

@ -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<Overlay>(getRenderItemID(), [newRenderItemIDs](Overlay& data) {
auto modelOverlay = static_cast<ModelOverlay*>(&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<Overlay>(getRenderItemID(), [](Overlay& data) {
auto modelOverlay = static_cast<ModelOverlay*>(&data);
modelOverlay->clearSubRenderItemIDs();
});
}
void ModelOverlay::setVisible(bool visible) {
@ -529,3 +539,19 @@ void ModelOverlay::copyAnimationJointDataToModel(QVector<JointData> 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;
}

View file

@ -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 };

View file

@ -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);

View file

@ -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(); }

View file

@ -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);
}
}

View file

@ -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() };

View file

@ -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;

View file

@ -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);

View file

@ -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; }

View file

@ -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()) {

View file

@ -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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& abortProcessing, int face) {
assert(image.format() == QIMAGE_HDR_FORMAT);
void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& 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<glm::vec4> data;
std::vector<glm::vec4>::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<const uint32*>( image.constScanLine(lineNb) );
const uint32* srcPixelIt = reinterpret_cast<const uint32*>(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<nvtt::OutputHandler> 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<bool>& 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<bool>& 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<const void*>(image.constBits());
const int width = localCopy.width(), height = localCopy.height();
const void* data = static_cast<const void*>(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<boo
nvtt::InputOptions inputOptions;
inputOptions.setTextureLayout(textureType, width, height);
inputOptions.setMipmapData(data, width, height);
// setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap
data = nullptr;
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
inputOptions.setFormat(inputFormat);
inputOptions.setGamma(inputGamma, outputGamma);
@ -641,14 +678,14 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
void generateMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& 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<bool>& 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<bool>& 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<bool>& 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<uint32(const glm::vec3&)> packFunc;
#ifdef DEBUG_COLOR_PACKING
std::function<glm::vec3(uint32)> 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<const QRgb*>( 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<const QRgb*>( localCopy.constScanLine(y) );
const QRgb* srcLineEnd = srcLineIt + localCopy.width();
uint32* hdrLineIt = reinterpret_cast<uint32*>( 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<bool>& 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<QImage> 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<QImage> 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);
}
}

View file

@ -41,45 +41,47 @@ enum Type {
UNUSED_TEXTURE
};
using TextureLoader = std::function<gpu::TexturePointer(const QImage&, const std::string&, const std::atomic<bool>&)>;
using TextureLoader = std::function<gpu::TexturePointer(QImage&&, const std::string&, const std::atomic<bool>&)>;
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<bool>& abortProcessing);
gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& abortProcessing = false);

View file

@ -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<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& 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;

View file

@ -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<params._blurKernelSize ; y++) {
uv = lineStartUv;
lineStartUv.y += deltaUv.y;
@ -70,8 +76,10 @@ void main(void) {
for (x=0 ; x<params._blurKernelSize ; x++) {
if (uv.x>=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@>

View file

@ -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;
}
}
}

View file

@ -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@>

View file

@ -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();
}

View file

@ -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" };

View file

@ -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 };
};
}

View file

@ -532,7 +532,7 @@ bool Scene::isSelectionEmpty(const Selection::Name& name) const {
std::unique_lock<std::mutex> lock(_selectionsMutex);
auto found = _selections.find(name);
if (found == _selections.end()) {
return false;
return true;
} else {
return (*found).second.isEmpty();
}

View file

@ -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());
}

View file

@ -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);

View file

@ -19,8 +19,14 @@
#include <QDir>
#include <QUrl>
#include <QtCore/QStandardPaths>
#include <QRegularExpression>
#include <mutex> // 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) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\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<QString> possibleExtensions) {
QString fileNameLowered = fileName.toLower();
foreach (const QString possibleExtension, possibleExtensions) {

View file

@ -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)

View file

@ -44,6 +44,12 @@ extern "C" FILE * __cdecl __iob_func(void) {
#include <CoreFoundation/CoreFoundation.h>
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
#include <signal.h>
#include <cerrno>
#endif
#include <QtCore/QDebug>
#include <QDateTime>
#include <QElapsedTimer>
@ -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";

View file

@ -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();

View file

@ -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)
}
}
}
}

View file

@ -1 +1,2 @@
PlotPerf 1.0 PlotPerf.qml
PlotPerf 1.0 PlotPerf.qml
Color 1.0 Color.qml

View file

@ -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) }
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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() }
}
}
}
}
}

View file

@ -0,0 +1 @@
HighlightStyle 1.0 HighlightStyle.qml

View file

@ -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
}
}
}
}
}
}

View file

@ -1 +0,0 @@
HighlightPage 1.0 HighlightPage.qml

View file

@ -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);
}());

View file

@ -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) {

View file

@ -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.");
}

2250
server-console/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@
""
],
"devDependencies": {
"electron-compilers": "^1.0.1",
"electron-packager": "^6.0.2",
"electron-prebuilt": "0.37.5"
},

View file

@ -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()

View file

@ -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 $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:auto-tester>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:auto-tester>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:auto-tester>
)
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} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
)
endif ()

View file

@ -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/

View file

@ -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 <cmath>
// 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;
};

View file

@ -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 <QtCore/QString>
#include <QImage>
class ImageComparer {
public:
double compareImages(QImage resultImage, QImage expectedImage) const;
};
#endif // hifi_ImageComparer_h

View file

@ -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 <assert.h>
#include <QtCore/QTextStream>
#include <QDirIterator>
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<QString> 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());
}

View file

@ -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 <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtCore/QRegularExpression>
#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

View file

@ -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 <QtCore/QString>
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

View file

@ -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 <QtWidgets/QApplication>
#include "ui/AutoTester.h"
int main(int argc, char *argv[]) {
QApplication application(argc, argv);
AutoTester autoTester;
autoTester.show();
return application.exec();
}

View file

@ -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);
}

View file

@ -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 <QtWidgets/QMainWindow>
#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

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoTesterClass</class>
<widget class="QMainWindow" name="AutoTesterClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>470</height>
</rect>
</property>
<property name="windowTitle">
<string>AutoTester</string>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QPushButton" name="closeButton">
<property name="geometry">
<rect>
<x>60</x>
<y>360</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
<widget class="QPushButton" name="createTestButton">
<property name="geometry">
<rect>
<x>60</x>
<y>270</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create Test</string>
</property>
</widget>
<widget class="QPushButton" name="evaluateTestsButton">
<property name="geometry">
<rect>
<x>60</x>
<y>20</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Evaluate Test</string>
</property>
</widget>
<widget class="QPushButton" name="createRecursiveScriptButton">
<property name="geometry">
<rect>
<x>60</x>
<y>210</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create Recursive Script</string>
</property>
</widget>
<widget class="QPushButton" name="evaluateTestsRecursivelyButton">
<property name="geometry">
<rect>
<x>60</x>
<y>75</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Evaluate Tests Recursively</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View file

@ -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 <QtCore/QFileInfo>
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();
}

View file

@ -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

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MismatchWindow</class>
<widget class="QDialog" name="MismatchWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1585</width>
<height>694</height>
</rect>
</property>
<property name="windowTitle">
<string>MismatchWindow</string>
</property>
<widget class="QLabel" name="expectedImage">
<property name="geometry">
<rect>
<x>20</x>
<y>170</y>
<width>720</width>
<height>362</height>
</rect>
</property>
<property name="text">
<string>expected image</string>
</property>
</widget>
<widget class="QLabel" name="resultImage">
<property name="geometry">
<rect>
<x>760</x>
<y>170</y>
<width>720</width>
<height>362</height>
</rect>
</property>
<property name="text">
<string>result image</string>
</property>
</widget>
<widget class="QLabel" name="resultFilename">
<property name="geometry">
<rect>
<x>760</x>
<y>90</y>
<width>800</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>result image filename</string>
</property>
</widget>
<widget class="QLabel" name="expectedFilename">
<property name="geometry">
<rect>
<x>40</x>
<y>90</y>
<width>700</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>expected image filename</string>
</property>
</widget>
<widget class="QLabel" name="imagePath">
<property name="geometry">
<rect>
<x>40</x>
<y>30</y>
<width>1200</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>image path</string>
</property>
</widget>
<widget class="QPushButton" name="passTestButton">
<property name="geometry">
<rect>
<x>30</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Pass</string>
</property>
</widget>
<widget class="QPushButton" name="failTestButton">
<property name="geometry">
<rect>
<x>330</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Fail</string>
</property>
</widget>
<widget class="QPushButton" name="abortTestsButton">
<property name="geometry">
<rect>
<x>630</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Abort Tests</string>
</property>
</widget>
<widget class="QLabel" name="errorLabel">
<property name="geometry">
<rect>
<x>810</x>
<y>600</y>
<width>720</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>similarity</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View file

@ -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()

View file

@ -12,6 +12,7 @@
#include <QObject>
#include <QImageReader>
#include <QtCore/QDebug>
#include <QFile>
#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);
}

View file

@ -13,22 +13,32 @@
#define hifi_BakerCLI_h
#include <QtCore/QObject>
#include <QDir>
#include <memory>
#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> _baker;
};
#endif // hifi_BakerCLI_h
#endif // hifi_BakerCLI_h

View file

@ -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();

View file

@ -14,6 +14,8 @@
#include <QtWidgets/QWidget>
#include <memory>
#include <Baker.h>
class BakeWidget : public QWidget {