From 29f083547527adbdc74c86b71c62af1caefd861c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 20 Dec 2017 16:06:19 -0800 Subject: [PATCH 01/73] making entity joints work again --- .../src/RenderableModelEntityItem.cpp | 4 ++- script-archive/dressing_room/doppelganger.js | 34 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e578e4858d..ce43aa4eb5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1259,7 +1259,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _currentTextures = newTextures; } } - + if(entity->_needsJointSimulation) { + entity->copyAnimationJointDataToModel(); + } entity->updateModelBounds(); if (model->isVisible() != _visible) { diff --git a/script-archive/dressing_room/doppelganger.js b/script-archive/dressing_room/doppelganger.js index 10f6468e9f..8ff62ff665 100644 --- a/script-archive/dressing_room/doppelganger.js +++ b/script-archive/dressing_room/doppelganger.js @@ -15,7 +15,7 @@ var TEST_MODEL_URL = 'https://s3.amazonaws.com/hifi-public/ozan/avatars/albert/a var MIRROR_JOINT_DATA = true; var MIRRORED_ENTITY_SCRIPT_URL = Script.resolvePath('mirroredEntity.js'); -var FREEZE_TOGGLER_SCRIPT_URL = Script.resolvePath('freezeToggler.js?' + Math.random(0, 1000)) +var FREEZE_TOGGLER_SCRIPT_URL = Script.resolvePath('freezeToggler.js?' + Math.random(0, 1000)); var THROTTLE = false; var THROTTLE_RATE = 100; var doppelgangers = []; @@ -24,8 +24,7 @@ function Doppelganger(avatar) { this.initialProperties = { name: 'Hifi-Doppelganger', type: 'Model', - modelURL: TEST_MODEL_URL, - // dimensions: getAvatarDimensions(avatar), + modelURL: MyAvatar.skeletonModelURL, position: putDoppelgangerAcrossFromAvatar(this, avatar), rotation: rotateDoppelgangerTowardAvatar(this, avatar), collisionsWillMove: false, @@ -33,7 +32,7 @@ function Doppelganger(avatar) { script: FREEZE_TOGGLER_SCRIPT_URL, userData: JSON.stringify({ grabbableKey: { - grabbable: false, + grabbable: true, wantsTrigger: true } }) @@ -48,7 +47,7 @@ function getJointData(avatar) { var jointNames = MyAvatar.jointNames; jointNames.forEach(function(joint, index) { var translation = MyAvatar.getJointTranslation(index); - var rotation = MyAvatar.getJointRotation(index) + var rotation = MyAvatar.getJointRotation(index); allJointData.push({ joint: joint, index: index, @@ -65,9 +64,10 @@ function setJointData(doppelganger, relativeXforms) { var i, l = relativeXforms.length; for (i = 0; i < l; i++) { jointRotations.push(relativeXforms[i].rot); + //Entities.setAbsoluteJointRotationInObjectFrame(doppelganger.id, i, relativeXforms[i].rot); } - Entities.setAbsoluteJointRotationsInObjectFrame(doppelganger.id, jointRotations); + Entities.setLocalJointRotations(doppelganger.id, jointRotations); return true; } @@ -127,7 +127,7 @@ var JOINT_MIRROR_NAME_MAP = { LeftHandPinky2: "RightHandPinky2", LeftHandPinky3: "RightHandPinky3", LeftHandPinky4: "RightHandPinky4", - LeftHandPinky: "RightHandPinky", + LeftHandPinky: "RightHandPinky" }; // maps joint names to parent joint names. @@ -192,7 +192,7 @@ var JOINT_PARENT_NAME_MAP = { LeftHandPinky1: "LeftHand", LeftHandPinky2: "LeftHandPinky1", LeftHandPinky3: "LeftHandPinky2", - LeftHandPinky: "LeftHandPinky3", + LeftHandPinky: "LeftHandPinky3" }; // maps joint indices to parent joint indices. @@ -395,7 +395,7 @@ function connectDoppelgangerUpdates() { } function disconnectDoppelgangerUpdates() { - print('SHOULD DISCONNECT') + print('SHOULD DISCONNECT'); if (isConnected === true) { Script.update.disconnect(updateDoppelganger); } @@ -465,13 +465,13 @@ function handleFreezeMessages(channel, message, sender) { } catch (e) { print('error parsing wearable message'); } - print('MESSAGE ACTION::' + parsedMessage.action) + print('MESSAGE ACTION::' + parsedMessage.action); if (parsedMessage.action === 'freeze') { - print('ACTUAL FREEZE') + print('ACTUAL FREEZE'); disconnectDoppelgangerUpdates(); } if (parsedMessage.action === 'unfreeze') { - print('ACTUAL UNFREEZE') + print('ACTUAL UNFREEZE'); connectDoppelgangerUpdates(); } @@ -496,7 +496,7 @@ function handleWearableMessages(channel, message, sender) { } catch (e) { print('error parsing wearable message'); } - print('parsed message!!!') + print('parsed message!!!'); if (channel === 'Hifi-Doppelganger-Wearable') { mirrorEntitiesForDoppelganger(doppelgangers[0], parsedMessage); @@ -511,13 +511,13 @@ function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { var doppelgangerProps = Entities.getEntityProperties(doppelganger.id); var action = parsedMessage.action; - print('IN MIRROR ENTITIES CALL' + action) + print('IN MIRROR ENTITIES CALL' + action); var baseEntity = parsedMessage.baseEntity; var wearableProps = Entities.getEntityProperties(baseEntity); - print('WEARABLE PROPS::') + print('WEARABLE PROPS::'); delete wearableProps.id; delete wearableProps.created; delete wearableProps.age; @@ -613,7 +613,7 @@ function getBaseEntityForMirrorEntity(mirrorEntity) { makeDoppelgangerForMyAvatar(); subscribeToWearableMessages(); -subscribeToWearableMessagesForAvatar(); +//subscribeToWearableMessagesForAvatar(); subscribeToFreezeMessages(); function cleanup() { @@ -626,4 +626,4 @@ function cleanup() { Entities.deleteEntity(doppelganger.id); }); } -Script.scriptEnding.connect(cleanup); \ No newline at end of file +Script.scriptEnding.connect(cleanup); From 7e1be2b17c9a1640aa1dc5fb314ecdcf589ab24a Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 21 Dec 2017 11:35:14 -0500 Subject: [PATCH 02/73] shared promises library --- libraries/shared/src/shared/MiniPromises.cpp | 4 + libraries/shared/src/shared/MiniPromises.h | 249 +++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 libraries/shared/src/shared/MiniPromises.cpp create mode 100644 libraries/shared/src/shared/MiniPromises.h diff --git a/libraries/shared/src/shared/MiniPromises.cpp b/libraries/shared/src/shared/MiniPromises.cpp new file mode 100644 index 0000000000..fdfb509608 --- /dev/null +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -0,0 +1,4 @@ +#include "MiniPromises.h" +namespace { + int promiseMetaTypeId = qRegisterMetaType("MiniPromise::Promise"); +} diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h new file mode 100644 index 0000000000..17f2394a2f --- /dev/null +++ b/libraries/shared/src/shared/MiniPromises.h @@ -0,0 +1,249 @@ +#pragma once + +// Minimalist threadsafe Promise-like helper for instrumenting asynchronous results +// +// This class pivots around composable continuation-style callback handlers: +// auto successCallback = [=](QVariantMap result) { .... } +// auto errorCallback = [=](QString error) { .... } +// auto combinedCallback = [=](QString error, QVariantMap result) { .... } +// +// * Callback Handlers are automatically invoked on the right thread (the Promise's thread). +// * Callbacks can be assigned anytime during a Promise's life and "do the right thing". +// - ie: for code clarity you can define success cases first (or maintain time order) +// * "Deferred" concept can be used to publish outcomes. +// * "Promise" concept be used to subscribe to outcomes. +// +// See AssetScriptingInterface.cpp for some examples of using to simplify chained async result. + +#include +#include +#include +#include +#include "ReadWriteLockable.h" +#include + +class MiniPromise : public QObject, public std::enable_shared_from_this, public ReadWriteLockable { + Q_OBJECT +public: + using handlerFunction = std::function; + using successFunction = std::function; + using errorFunction = std::function; + using handlers = QVector; + using Promise = std::shared_ptr; + MiniPromise(const QString debugName) { setObjectName(debugName); } + MiniPromise() {} + ~MiniPromise() { + qDebug() << "~MiniPromise" << objectName(); + if (!_rejected && !_resolved) { + qWarning() << "====== WARNING: unhandled MiniPromise" << objectName() << _error << _result; + } + } + + QString _error; + QVariantMap _result; + std::atomic _rejected{false}; + std::atomic _resolved{false}; + handlers _onresolve; + handlers _onreject; + handlers _onfinally; + + Promise self() { return shared_from_this(); } + + // result aggregation helpers -- eg: deferred->defaults(interimResultMap)->ready(...) + + // copy values from the input map, but only for keys that don't already exist + Promise mixin(const QVariantMap& source) { + qDebug() << objectName() << "mixin"; + withWriteLock([&]{ + for (const auto& key : source.keys()) { + if (!_result.contains(key)) { + _result[key] = source[key]; + } + } + }); + return self(); + } + // copy values from the input map, replacing any existing keys + Promise assignResult(const QVariantMap& source) { + qDebug() << objectName() << "assignResult"; + withWriteLock([&]{ + for (const auto& key : source.keys()) { + _result[key] = source[key]; + } + }); + return self(); + } + + // TODO: I think calling as "ready" makes it read better, but is customary Promise "finally" sufficient? + Promise ready(handlerFunction always) { return finally(always); } + Promise finally(handlerFunction always) { + if (!_rejected && !_resolved) { + withWriteLock([&]{ + _onfinally << always; + }); + } else { + qDebug() << "finally (already resolved/rejected)" << objectName(); + executeOnPromiseThread([&]{ + withReadLock([&]{ + always(_error, _result); + }); + }); + } + return self(); + } + Promise fail(errorFunction errorOnly) { + return fail([this, errorOnly](QString error, QVariantMap result) { + errorOnly(error); + }); + } + + Promise fail(handlerFunction failFunc) { + if (!_rejected) { + withWriteLock([&]{ + _onreject << failFunc; + }); + } else { + executeOnPromiseThread([&]{ + qDebug() << "fail (already rejected)" << objectName(); + withReadLock([&]{ + failFunc(_error, _result); + }); + }); + } + return self(); + } + + Promise then(successFunction successOnly) { + return then([this, successOnly](QString error, QVariantMap result) { + successOnly(result); + }); + } + Promise then(handlerFunction successFunc) { + if (!_resolved) { + withWriteLock([&]{ + _onresolve << successFunc; + }); + } else { + executeOnPromiseThread([&]{ + qDebug() << "fail (already resolved)" << objectName(); + withReadLock([&]{ + successFunc(_error, _result); + }); + }); + } + return self(); + } + // register combined success/error handlers + Promise then(successFunction successOnly, errorFunction errorOnly) { + // note: first arg can be null (see ES6 .then(null, errorHandler) conventions) + if (successOnly) { + then(successOnly); + } + if (errorOnly) { + fail(errorOnly); + } + return self(); + } + + // trigger methods + Promise handle(QString error, const QVariantMap& result) { + qDebug() << "handle" << objectName() << error; + if (error.isEmpty()) { + resolve(error, result); + } else { + reject(error, result); + } + return self(); + } + Promise resolve(QVariantMap result) { + return resolve(QString(), result); + } + + Q_INVOKABLE void executeOnPromiseThread(std::function function) { + if (QThread::currentThread() != thread()) { + qDebug() << "-0-0-00-0--0" << objectName() << "executeOnPromiseThread -- wrong thread" << QThread::currentThread(); + QMetaObject::invokeMethod( + this, "executeOnPromiseThread", Qt::BlockingQueuedConnection, + Q_ARG(std::function, function)); + } else { + function(); + } + } + + Promise setState(bool resolved, QString error, const QVariantMap& result) { + qDebug() << "setState" << objectName() << resolved << error; + if (resolved) { + _resolved = true; + } else { + _rejected = true; + } + withWriteLock([&]{ + _error = error; + }); + assignResult(result); + qDebug() << "//setState" << objectName() << resolved << error; + return self(); + } + Promise resolve(QString error, const QVariantMap& result) { + setState(true, error, result); + qDebug() << "handle" << objectName() << error; + { + QString error; + QVariantMap result; + handlers toresolve; + handlers tofinally; + withReadLock([&]{ + error = _error; + result = _result; + toresolve = _onresolve; + tofinally = _onfinally; + }); + executeOnPromiseThread([&]{ + for (const auto& onresolve : toresolve) { + onresolve(error, result); + } + for (const auto& onfinally : tofinally) { + onfinally(error, result); + } + }); + } + return self(); + } + Promise reject(QString error) { + return reject(error, QVariantMap()); + } + Promise reject(QString error, const QVariantMap& result) { + setState(false, error, result); + qDebug() << "handle" << objectName() << error; + { + QString error; + QVariantMap result; + handlers toreject; + handlers tofinally; + withReadLock([&]{ + error = _error; + result = _result; + toreject = _onreject; + tofinally = _onfinally; + }); + executeOnPromiseThread([&]{ + for (const auto& onreject : toreject) { + onreject(error, result); + } + for (const auto& onfinally : tofinally) { + onfinally(error, result); + } + if (toreject.isEmpty() && tofinally.isEmpty()) { + qWarning() << "WARNING: unhandled MiniPromise::reject" << objectName() << error << result; + } + }); + } + return self(); + } +}; + +inline MiniPromise::Promise makePromise(const QString& hint = QString()) { + return std::make_shared(hint); +} + +Q_DECLARE_METATYPE(MiniPromise::Promise) From 8c7a8f0df348c1a94e450c19e3b8a0d30ec77548 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 15 Dec 2017 14:12:04 -0500 Subject: [PATCH 03/73] expanded Assets scripting interface --- libraries/networking/src/AssetClient.cpp | 25 ++ libraries/networking/src/AssetClient.h | 2 + libraries/networking/src/AssetRequest.cpp | 11 + libraries/networking/src/AssetRequest.h | 3 +- .../networking/src/AssetResourceRequest.cpp | 2 +- libraries/networking/src/AssetUtils.cpp | 33 +- libraries/networking/src/AssetUtils.h | 17 +- .../src/BaseAssetScriptingInterface.cpp | 399 ++++++++++++++++++ .../src/BaseAssetScriptingInterface.h | 78 ++++ .../src/AssetScriptingInterface.cpp | 349 +++++++++++++-- .../src/AssetScriptingInterface.h | 37 +- .../tests/unit_tests/assetUnitTests.js | 181 ++++++++ 12 files changed, 1090 insertions(+), 47 deletions(-) create mode 100644 libraries/networking/src/BaseAssetScriptingInterface.cpp create mode 100644 libraries/networking/src/BaseAssetScriptingInterface.h create mode 100644 scripts/developer/tests/unit_tests/assetUnitTests.js diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 6fdfc5a42b..725975c9fb 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "AssetRequest.h" #include "AssetUpload.h" @@ -72,6 +73,30 @@ void AssetClient::init() { networkAccessManager.setCache(cache); qInfo() << "ResourceManager disk cache setup at" << _cacheDir << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; + } else { + auto cache = qobject_cast(networkAccessManager.cache()); + qInfo() << "ResourceManager disk cache already setup at" << cache->cacheDirectory() + << "(size:" << cache->maximumCacheSize() / BYTES_PER_GIGABYTES << "GB)"; + } + +} + +void AssetClient::cacheInfoRequest(MiniPromise::Promise deferred) { + if (QThread::currentThread() != thread()) { + if (!QMetaType::isRegistered(qMetaTypeId())) { + qRegisterMetaType(); + } + QMetaObject::invokeMethod(this, "cacheInfoRequest", Q_ARG(MiniPromise::Promise, deferred)); + return; + } + if (auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache())) { + deferred->resolve({ + { "cacheDirectory", cache->cacheDirectory() }, + { "cacheSize", cache->cacheSize() }, + { "maximumCacheSize", cache->maximumCacheSize() }, + }); + } else { + deferred->reject("Cache not available"); } } diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 8035aa886e..5d854390e2 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -19,6 +19,7 @@ #include #include +#include #include "AssetUtils.h" #include "ByteRange.h" @@ -66,6 +67,7 @@ public slots: void init(); void cacheInfoRequest(QObject* reciever, QString slot); + void cacheInfoRequest(MiniPromise::Promise deferred); void clearCache(); private slots: diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 7fa563d4ad..6a2bcdca9c 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -134,3 +134,14 @@ void AssetRequest::start() { emit progress(totalReceived, total); }); } + + +const QString AssetRequest::getErrorString() const { + QString result; + if (_error != Error::NoError) { + QVariant v; + v.setValue(_error); + result = v.toString(); // courtesy of Q_ENUM + } + return result; +} diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h index a7213a90d7..b469e2f012 100644 --- a/libraries/networking/src/AssetRequest.h +++ b/libraries/networking/src/AssetRequest.h @@ -42,7 +42,7 @@ public: NetworkError, UnknownError }; - + Q_ENUM(Error) AssetRequest(const QString& hash, const ByteRange& byteRange = ByteRange()); virtual ~AssetRequest() override; @@ -51,6 +51,7 @@ public: const QByteArray& getData() const { return _data; } const State& getState() const { return _state; } const Error& getError() const { return _error; } + const QString getErrorString() const; QUrl getUrl() const { return ::getATPUrl(_hash); } QString getHash() const { return _hash; } diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 55d0ad4f75..e1a155a561 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -146,7 +146,7 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) { _assetRequest = assetClient->createRequest(hash, _byteRange); connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress); - connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) { + connect(_assetRequest, &AssetRequest::finished, this, [this, hash](AssetRequest* req) { Q_ASSERT(_state == InProgress); Q_ASSERT(req == _assetRequest); Q_ASSERT(req->getState() == AssetRequest::Finished); diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index 76fda6aed4..fa9885f053 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -15,6 +15,7 @@ #include #include +#include // for baseName #include #include "NetworkAccessManager.h" @@ -22,8 +23,36 @@ #include "ResourceManager.h" -QUrl getATPUrl(const QString& hash) { - return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP, hash)); +// Extract the valid AssetHash portion from atp: URLs like "[atp:]HASH[.fbx][?query]" +// (or an invalid AssetHash if not found) +AssetHash extractAssetHash(const QString& input) { + if (isValidHash(input)) { + return input; + } + QString path = getATPUrl(input).path(); + QString baseName = QFileInfo(path).baseName(); + if (isValidHash(baseName)) { + return baseName; + } + return AssetHash(); +} + +// Get the normalized ATP URL for a raw hash, /path or "atp:" input string. +QUrl getATPUrl(const QString& input) { + QUrl url = input; + if (!url.scheme().isEmpty() && url.scheme() != URL_SCHEME_ATP) { + return QUrl(); + } + // this strips extraneous info from the URL (while preserving fragment/querystring) + QString path = url.toEncoded( + QUrl::RemoveAuthority | QUrl::RemoveScheme | + QUrl::StripTrailingSlash | QUrl::NormalizePathSegments + ); + QString baseName = QFileInfo(path).baseName(); + if (isValidPath(path) || isValidHash(baseName)) { + return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP).arg(path)); + } + return QUrl(); } QByteArray hashData(const QByteArray& data) { diff --git a/libraries/networking/src/AssetUtils.h b/libraries/networking/src/AssetUtils.h index a7c053c3d6..8c89b4701c 100644 --- a/libraries/networking/src/AssetUtils.h +++ b/libraries/networking/src/AssetUtils.h @@ -71,7 +71,8 @@ struct MappingInfo { using AssetMapping = std::map; -QUrl getATPUrl(const QString& hash); +QUrl getATPUrl(const QString& input); +AssetHash extractAssetHash(const QString& input); QByteArray hashData(const QByteArray& data); @@ -84,4 +85,18 @@ bool isValidHash(const QString& hashString); QString bakingStatusToString(BakingStatus status); +// backwards-compatible namespace aliases +// (allows new code to be explicit -- eg: `AssetUtils::isValidPath(path)` vs. `isValidPath(path)`) +namespace AssetUtils { + static const auto& loadFromCache = ::loadFromCache; + static const auto& saveToCache = ::saveToCache; + static const auto& hashData = ::hashData; + static const auto& getATPUrl = ::getATPUrl; + static const auto& extractAssetHash = ::extractAssetHash; + static const auto& isValidFilePath = ::isValidFilePath; + static const auto& isValidPath = ::isValidPath; + static const auto& isValidHash = ::isValidHash; +}; + + #endif // hifi_AssetUtils_h diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp new file mode 100644 index 0000000000..bfdc21c7d5 --- /dev/null +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -0,0 +1,399 @@ +// +// BaseAssetScriptingInterface.cpp +// libraries/networking/src +// +// 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 + +#include "BaseAssetScriptingInterface.h" + +#include +#include +#include +#include + +#include "AssetRequest.h" +#include "AssetUpload.h" +#include "AssetUtils.h" +#include "MappingRequest.h" +#include "NetworkLogging.h" + +#include + +#include +#include "Gzip.h" + +using Promise = MiniPromise::Promise; + +QSharedPointer BaseAssetScriptingInterface::assetClient() { + return DependencyManager::get(); +} + +BaseAssetScriptingInterface::BaseAssetScriptingInterface(QObject* parent) : QObject(parent), _cache(this) { +} + + +bool BaseAssetScriptingInterface::initializeCache() { + qDebug() << "BaseAssetScriptingInterface::getCacheStatus -- current values" << _cache.cacheDirectory() << _cache.cacheSize() << _cache.maximumCacheSize(); + + // NOTE: *instances* of QNetworkDiskCache are not thread-safe -- however, different threads can effectively + // use the same underlying cache if configured with identical settings. Once AssetClient's disk cache settings + // become available we configure our instance to match. + auto assets = assetClient(); + if (!assets) { + return false; // not yet possible to initialize the cache + } + if (_cache.cacheDirectory().size()) { + return true; // cache is ready + } + + // attempt to initialize the cache + qDebug() << "BaseAssetScriptingInterface::getCacheStatus -- invoking AssetClient::init" << assetClient().data(); + QMetaObject::invokeMethod(assetClient().data(), "init"); + + qDebug() << "BaseAssetScriptingInterface::getCacheStatus querying cache status" << QThread::currentThread(); + Promise deferred = makePromise("BaseAssetScriptingInterface--queryCacheStatus"); + deferred->then([&](QVariantMap result) { + qDebug() << "//queryCacheStatus" << QThread::currentThread(); + auto cacheDirectory = result.value("cacheDirectory").toString(); + auto cacheSize = result.value("cacheSize").toLongLong(); + auto maximumCacheSize = result.value("maximumCacheSize").toLongLong(); + qDebug() << "///queryCacheStatus" << cacheDirectory << cacheSize << maximumCacheSize; + _cache.setCacheDirectory(cacheDirectory); + _cache.setMaximumCacheSize(maximumCacheSize); + }); + deferred->fail([&](QString error) { + qDebug() << "//queryCacheStatus ERROR" << QThread::currentThread() << error; + }); + assets->cacheInfoRequest(deferred); + return false; // cache is not ready yet +} + +QVariantMap BaseAssetScriptingInterface::getCacheStatus() { + //assetClient()->cacheInfoRequest(this, "onCacheInfoResponse"); + return { + { "cacheDirectory", _cache.cacheDirectory() }, + { "cacheSize", _cache.cacheSize() }, + { "maximumCacheSize", _cache.maximumCacheSize() }, + }; +} + +QVariantMap BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) { + QNetworkCacheMetaData metaData = _cache.metaData(url); + QVariantMap attributes, rawHeaders; + + QHashIterator i(metaData.attributes()); + while (i.hasNext()) { + i.next(); + attributes[QString::number(i.key())] = i.value(); + } + for (const auto& i : metaData.rawHeaders()) { + rawHeaders[i.first] = i.second; + } + return { + { "isValid", metaData.isValid() }, + { "url", metaData.url() }, + { "expirationDate", metaData.expirationDate() }, + { "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() }, + { "saveToDisk", metaData.saveToDisk() }, + { "attributes", attributes }, + { "rawHeaders", rawHeaders }, + }; +} + +QVariantMap BaseAssetScriptingInterface::loadFromCache(const QUrl& url) { + qDebug() << "loadFromCache" << url; + QVariantMap result = { + { "metadata", queryCacheMeta(url) }, + { "data", QByteArray() }, + }; + // caller is responsible for the deletion of the ioDevice, hence the unique_ptr + if (auto ioDevice = std::unique_ptr(_cache.data(url))) { + QByteArray data = ioDevice->readAll(); + qCDebug(asset_client) << url.toDisplayString() << "loaded from disk cache (" << data.size() << " bytes)"; + result["data"] = data; + } + return result; +} + +namespace { + // parse RFC 1123 HTTP date format + QDateTime parseHttpDate(const QString& dateString) { + QDateTime dt = QDateTime::fromString(dateString.left(25), "ddd, dd MMM yyyy HH:mm:ss"); + dt.setTimeSpec(Qt::UTC); + return dt; + } +} + +bool BaseAssetScriptingInterface::saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& headers) { + QDateTime lastModified = headers.contains("last-modified") ? + parseHttpDate(headers["last-modified"].toString()) : + QDateTime::currentDateTimeUtc(); + QDateTime expirationDate = headers.contains("expires") ? + parseHttpDate(headers["expires"].toString()) : + QDateTime(); // never expires + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + metaData.setSaveToDisk(true); + metaData.setLastModified(lastModified); + metaData.setExpirationDate(expirationDate); + if (auto ioDevice = _cache.prepare(metaData)) { + ioDevice->write(data); + _cache.insert(ioDevice); + qCDebug(asset_client) << url.toDisplayString() << "saved to disk cache ("<< data.size()<<" bytes)"; + return true; + } + qCWarning(asset_client) << "Could not save" << url.toDisplayString() << "to disk cache."; + return false; +} + +Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, QString responseType) { + auto hash = AssetUtils::extractAssetHash(asset); + auto url = AssetUtils::getATPUrl(hash).toString(); + + QVariantMap metaData = { + { "_asset", asset }, + { "_type", "download" }, + { "hash", hash }, + { "url", url }, + { "responseType", responseType }, + }; + + Promise fetched = makePromise("loadAsset::fetched"), + loaded = makePromise("loadAsset::loaded"); + + downloadBytes(hash) + ->mixin(metaData) + ->ready([=](QString error, QVariantMap result) { + Q_ASSERT(thread() == QThread::currentThread()); + fetched->mixin(result); + if (decompress) { + qDebug() << "loadAsset::decompressBytes..."; + decompressBytes(result.value("data").toByteArray()) + ->mixin(result) + ->ready([=](QString error, QVariantMap result) { + fetched->handle(error, result); + }); + } else { + fetched->handle(error, result); + } + }); + + fetched->ready([=](QString error, QVariantMap result) { + qDebug() << "loadAsset::fetched" << error; + if (responseType == "arraybuffer") { + loaded->resolve(NoError, result); + } else { + convertBytes(result.value("data").toByteArray(), responseType) + ->mixin(result) + ->ready([=](QString error, QVariantMap result) { + loaded->resolve(NoError, result); + }); + } + }); + + return loaded; +} + +Promise BaseAssetScriptingInterface::convertBytes(const QByteArray& dataByteArray, const QString& responseType) { + QVariantMap result; + Promise conversion = makePromise(__FUNCTION__); + if (dataByteArray.size() == 0) { + result["response"] = QString(); + } else if (responseType == "text") { + result["response"] = QString::fromUtf8(dataByteArray); + } else if (responseType == "json") { + QJsonParseError status; + auto parsed = QJsonDocument::fromJson(dataByteArray, &status); + if (status.error == QJsonParseError::NoError) { + result["response"] = parsed.isArray() ? + QVariant(parsed.array().toVariantList()) : + QVariant(parsed.object().toVariantMap()); + } else { + QVariantMap errorResult = { + { "error", status.error }, + { "offset", status.offset }, + }; + return conversion->reject("JSON Parse Error: " + status.errorString(), errorResult); + } + } else if (responseType == "arraybuffer") { + result["response"] = dataByteArray; + } + return conversion->resolve(NoError, result); +} + +Promise BaseAssetScriptingInterface::decompressBytes(const QByteArray& dataByteArray) { + QByteArray inflated; + auto start = usecTimestampNow(); + if (gunzip(dataByteArray, inflated)) { + auto end = usecTimestampNow(); + return makePromise(__FUNCTION__)->resolve(NoError, { + { "_compressedByteLength", dataByteArray.size() }, + { "_compressedContentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() }, + { "_compressMS", (double)(end - start) / 1000.0 }, + { "decompressed", true }, + { "byteLength", inflated.size() }, + { "contentType", QMimeDatabase().mimeTypeForData(inflated).name() }, + { "data", inflated }, + }); + } else { + return makePromise(__FUNCTION__)->reject("gunzip error", {}); + } +} + +Promise BaseAssetScriptingInterface::compressBytes(const QByteArray& dataByteArray, int level) { + QByteArray deflated; + auto start = usecTimestampNow(); + if (gzip(dataByteArray, deflated, level)) { + auto end = usecTimestampNow(); + return makePromise(__FUNCTION__)->resolve(NoError, { + { "_uncompressedByteLength", dataByteArray.size() }, + { "_uncompressedContentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() }, + { "_compressMS", (double)(end - start) / 1000.0 }, + { "compressed", true }, + { "byteLength", deflated.size() }, + { "contentType", QMimeDatabase().mimeTypeForData(deflated).name() }, + { "data", deflated }, + }); + } else { + return makePromise(__FUNCTION__)->reject("gzip error", {}); + } +} + +Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { + auto assetClient = DependencyManager::get(); + QPointer assetRequest = assetClient->createRequest(hash); + Promise deferred = makePromise(__FUNCTION__); + + QObject::connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { + qDebug() << "...BaseAssetScriptingInterface::downloadBytes" << request->getErrorString(); + // note: we are now on the "Resource Manager" thread + Q_ASSERT(QThread::currentThread() == request->thread()); + Q_ASSERT(request->getState() == AssetRequest::Finished); + QString error; + QVariantMap result; + if (request->getError() == AssetRequest::Error::NoError) { + QByteArray data = request->getData(); + result = { + { "url", request->getUrl() }, + { "hash", request->getHash() }, + { "cached", request->loadedFromCache() }, + { "content-type", QMimeDatabase().mimeTypeForData(data).name() }, + { "data", data }, + }; + } else { + error = request->getError(); + result = { { "error", request->getError() } }; + } + qDebug() << "//BaseAssetScriptingInterface::downloadBytes" << error << result.keys(); + // forward thread-safe copies back to our thread + deferred->handle(error, result); + request->deleteLater(); + }); + assetRequest->start(); + return deferred; +} + +Promise BaseAssetScriptingInterface::uploadBytes(const QByteArray& bytes) { + Promise deferred = makePromise(__FUNCTION__); + QPointer upload = DependencyManager::get()->createUpload(bytes); + + QObject::connect(upload, &AssetUpload::finished, upload, [=](AssetUpload* upload, const QString& hash) { + Q_ASSERT(QThread::currentThread() == upload->thread()); + // note: we are now on the "Resource Manager" thread + QString error; + QVariantMap result; + if (upload->getError() == AssetUpload::NoError) { + result = { + { "hash", hash }, + { "url", AssetUtils::getATPUrl(hash).toString() }, + { "filename", upload->getFilename() }, + }; + } else { + error = upload->getErrorString(); + result = { { "error", upload->getError() } }; + } + // forward thread-safe copies back to our thread + deferred->handle(error, result); + upload->deleteLater(); + }); + upload->start(); + return deferred; +} + +Promise BaseAssetScriptingInterface::getAssetInfo(QString asset) { + auto deferred = makePromise(__FUNCTION__); + auto url = AssetUtils::getATPUrl(asset); + auto path = url.path(); + auto hash = AssetUtils::extractAssetHash(asset); + if (AssetUtils::isValidHash(hash)) { + // already a valid ATP hash -- nothing to do + deferred->resolve(NoError, { + { "hash", hash }, + { "path", path }, + { "url", url }, + }); + } else if (AssetUtils::isValidFilePath(path)) { + auto assetClient = DependencyManager::get(); + QPointer request = assetClient->createGetMappingRequest(path); + + QObject::connect(request, &GetMappingRequest::finished, request, [=]() { + Q_ASSERT(QThread::currentThread() == request->thread()); + // note: we are now on the "Resource Manager" thread + QString error; + QVariantMap result; + if (request->getError() == GetMappingRequest::NoError) { + result = { + { "_hash", hash }, + { "_path", path }, + { "_url", url }, + { "hash", request->getHash() }, + { "wasRedirected", request->wasRedirected() }, + { "path", request->wasRedirected() ? request->getRedirectedPath() : path }, + }; + } else { + error = request->getErrorString(); + result = { { "error", request->getError() } }; + } + // forward thread-safe copies back to our thread + deferred->handle(error, result); + request->deleteLater(); + }); + request->start(); + } else { + deferred->reject("invalid ATP file path: " + asset + "("+path+")", {}); + } + return deferred; +} + +Promise BaseAssetScriptingInterface::symlinkAsset(QString hash, QString path) { + auto deferred = makePromise(__FUNCTION__); + auto assetClient = DependencyManager::get(); + QPointer setMappingRequest = assetClient->createSetMappingRequest(path, hash); + + connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [=](SetMappingRequest* request) { + Q_ASSERT(QThread::currentThread() == request->thread()); + // we are now on the "Resource Manager" thread + QString error; + QVariantMap result; + if (request->getError() == SetMappingRequest::NoError) { + result = { + { "_hash", hash }, + { "_path", path }, + { "hash", request->getHash() }, + { "path", request->getPath() }, + { "url", AssetUtils::getATPUrl(request->getPath()).toString() }, + }; + } else { + error = request->getErrorString(); + result = { { "error", request->getError() } }; + } + // forward results back to our thread + deferred->handle(error, result); + request->deleteLater(); + }); + setMappingRequest->start(); + return deferred; +} diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h new file mode 100644 index 0000000000..3aca3d394e --- /dev/null +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -0,0 +1,78 @@ +// +// BaseAssetScriptingInterface.h +// libraries/networking/src +// +// 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 +// + +// BaseAssetScriptingInterface contains the engine-agnostic support code that can be used from +// both QML JS and QScriptEngine JS engine implementations + +#ifndef hifi_BaseAssetScriptingInterface_h +#define hifi_BaseAssetScriptingInterface_h + +#include +#include +#include "AssetClient.h" +#include +#include "NetworkAccessManager.h" +#include + +/**jsdoc + * @namespace Assets + */ +class BaseAssetScriptingInterface : public QObject { + Q_OBJECT +public: + using Promise = MiniPromise::Promise; + QSharedPointer assetClient(); + + BaseAssetScriptingInterface(QObject* parent = nullptr); + +public slots: + /**jsdoc + * Get the current status of the disk cache (if available) + * @function Assets.uploadData + * @static + * @return {String} result.cacheDirectory (path to the current disk cache) + * @return {Number} result.cacheSize (used cache size in bytes) + * @return {Number} result.maximumCacheSize (maxmimum cache size in bytes) + */ + QVariantMap getCacheStatus(); + + /**jsdoc + * Initialize the disk cache (returns true if already initialized) + * @function Assets.initializeCache + * @static + */ + bool initializeCache(); + + virtual bool isValidPath(QString input) { return AssetUtils::isValidPath(input); } + virtual bool isValidFilePath(QString input) { return AssetUtils::isValidFilePath(input); } + QUrl getATPUrl(QString input) { return AssetUtils::getATPUrl(input); } + QString extractAssetHash(QString input) { return AssetUtils::extractAssetHash(input); } + bool isValidHash(QString input) { return AssetUtils::isValidHash(input); } + QByteArray hashData(const QByteArray& data) { return AssetUtils::hashData(data); } + + virtual QVariantMap queryCacheMeta(const QUrl& url); + virtual QVariantMap loadFromCache(const QUrl& url); + virtual bool saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); + +protected: + //void onCacheInfoResponse(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize); + QNetworkDiskCache _cache; + const QString NoError{}; + //virtual bool jsAssert(bool condition, const QString& error) = 0; + Promise loadAsset(QString asset, bool decompress, QString responseType); + Promise getAssetInfo(QString asset); + Promise downloadBytes(QString hash); + Promise uploadBytes(const QByteArray& bytes); + Promise compressBytes(const QByteArray& bytes, int level = -1); + Promise convertBytes(const QByteArray& dataByteArray, const QString& responseType); + Promise decompressBytes(const QByteArray& bytes); + Promise symlinkAsset(QString hash, QString path); +}; +#endif // hifi_BaseAssetScriptingInterface_h diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 25e8c0dcf3..b5492db304 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -11,43 +11,70 @@ #include "AssetScriptingInterface.h" +#include +#include #include #include #include +#include #include #include #include -AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) : - _engine(engine) -{ +#include + +#include +#include "Gzip.h" +#include "ScriptEngine.h" + +//using Promise = MiniPromise::Promise; + +AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) { + if (auto engine = qobject_cast(parent)) { + registerMetaTypes(engine); + } } +#define JS_ASSERT(cond, error) { if (!this->jsAssert(cond, error)) { return; } } void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { + auto handler = makeScopedHandlerObject(thisObject(), callback); QByteArray dataByteArray = data.toUtf8(); auto upload = DependencyManager::get()->createUpload(dataByteArray); - QObject::connect(upload, &AssetUpload::finished, this, [this, callback](AssetUpload* upload, const QString& hash) mutable { - if (callback.isFunction()) { - QString url = "atp:" + hash; - QScriptValueList args { url, hash }; - callback.call(_engine->currentContext()->thisObject(), args); - } + Promise deferred = makePromise(__FUNCTION__) + ->ready([this, handler](QString error, QVariantMap result) { + auto url = result.value("url").toString(); + auto hash = result.value("hash").toString(); + jsCallback(handler, url, hash); + }); + + connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) { + // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly) + Q_ASSERT(QThread::currentThread() == upload->thread()); + deferred->resolve(NoError, { + { "url", "atp:" + hash }, + { "hash", hash }, + }); upload->deleteLater(); }); upload->start(); } void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) { - auto setMappingRequest = DependencyManager::get()->createSetMappingRequest(path, hash); + auto handler = makeScopedHandlerObject(thisObject(), callback); + auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash); + Promise deferred = makePromise(__FUNCTION__) + ->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); - QObject::connect(setMappingRequest, &SetMappingRequest::finished, this, [this, callback](SetMappingRequest* request) mutable { - if (callback.isFunction()) { - QString error = request->getErrorString(); - QScriptValueList args { error }; - callback.call(_engine->currentContext()->thisObject(), args); - } + connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [this, deferred](SetMappingRequest* request) { + Q_ASSERT(QThread::currentThread() == request->thread()); + // we are now on the "Resource Manager" thread + QString error = request->getErrorString(); + // forward a thread-safe values back to our thread + deferred->handle(error, { { "error", request->getError() } }); request->deleteLater(); }); setMappingRequest->start(); @@ -55,48 +82,63 @@ void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValu void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) { + // FIXME: historically this API method failed silently when given a non-atp prefixed + // urlString (or if the AssetRequest failed). + // .. is that by design or could we update without breaking things to provide better feedback to scripts? if (!urlString.startsWith(ATP_SCHEME)) { + // ... for now at least log a message so user can check logs + qDebug() << "AssetScriptingInterface::downloadData ERROR: url does not start with " << ATP_SCHEME; return; } - - // Make request to atp - auto path = urlString.right(urlString.length() - ATP_SCHEME.length()); - auto parts = path.split(".", QString::SkipEmptyParts); - auto hash = parts.length() > 0 ? parts[0] : ""; - + QString hash = AssetUtils::extractAssetHash(urlString); + auto handler = makeScopedHandlerObject(thisObject(), callback); auto assetClient = DependencyManager::get(); auto assetRequest = assetClient->createRequest(hash); - _pendingRequests << assetRequest; + Promise deferred = makePromise(__FUNCTION__) + ->ready([=](QString error, QVariantMap result) { + // FIXME: to remain backwards-compatible the signature here is "callback(data, n/a)" + jsCallback(handler, result.value("data").toString(), { { "errorMessage", error } }); + }); - connect(assetRequest, &AssetRequest::finished, this, [this, callback](AssetRequest* request) mutable { + connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { + Q_ASSERT(QThread::currentThread() == request->thread()); + // we are now on the "Resource Manager" thread Q_ASSERT(request->getState() == AssetRequest::Finished); if (request->getError() == AssetRequest::Error::NoError) { - if (callback.isFunction()) { - QString data = QString::fromUtf8(request->getData()); - QScriptValueList args { data }; - callback.call(_engine->currentContext()->thisObject(), args); - } + QString data = QString::fromUtf8(request->getData()); + // forward a thread-safe values back to our thread + deferred->resolve(NoError, { { "data", data } }); + } else { + // FIXME: propagate error to scripts? (requires changing signature or inverting param order above..) + //deferred->resolve(request->getErrorString(), { { "error", requet->getError() } }); + qDebug() << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString(); } request->deleteLater(); - _pendingRequests.remove(request); }); assetRequest->start(); } void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScriptValue callback) { + auto handler = makeScopedHandlerObject(thisObject(), callback); auto setBakingEnabledRequest = DependencyManager::get()->createSetBakingEnabledRequest({ path }, enabled); - QObject::connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, this, [this, callback](SetBakingEnabledRequest* request) mutable { - if (callback.isFunction()) { - QString error = request->getErrorString(); - QScriptValueList args{ error }; - callback.call(_engine->currentContext()->thisObject(), args); - } + Promise deferred = makePromise(__FUNCTION__) + ->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); + + connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) { + Q_ASSERT(QThread::currentThread() == request->thread()); + // we are now on the "Resource Manager" thread + + QString error = request->getErrorString(); + // forward thread-safe values back to our thread + deferred->handle(error, {}); request->deleteLater(); }); setBakingEnabledRequest->start(); @@ -111,3 +153,238 @@ void AssetScriptingInterface::sendFakedHandshake() { } #endif + +void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) { + auto path = AssetUtils::getATPUrl(asset).path(); + auto handler = makeScopedHandlerObject(thisObject(), callback); + JS_ASSERT(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")"); + JS_ASSERT(callback.isFunction(), "expected second parameter to be a callback function"); + qDebug() << ">>getMapping//getAssetInfo" << path; + getAssetInfo(path)->ready([this, handler](QString error, QVariantMap result) { + qDebug() << "//getMapping//getAssetInfo" << error << result.keys(); + jsCallback(handler, error, result.value("hash").toString()); + }); +} + +/////////////////////////// new APIS //////////////////////////////////// + +bool AssetScriptingInterface::jsAssert(bool condition, const QString& error) { + if (condition) { + return true; + } + if (context()) { + context()->throwError(error); + } else { + qDebug() << "WARNING -- jsAssert failed outside of a valid JS context: " + error; + } + return false; +} + +void AssetScriptingInterface::jsCallback(const QScriptValue& handler, + const QScriptValue& error, const QScriptValue& result) { + Q_ASSERT(thread() == QThread::currentThread()); + auto errorValue = !error.toBool() ? QScriptValue::NullValue : error; + JS_ASSERT(handler.isObject() && handler.property("callback").isFunction(), + QString("jsCallback -- .callback is not a function (%1)") + .arg(handler.property("callback").toVariant().typeName())); +#if 1 || DEGUG_JSCALLBACK + QScriptValue debug = result; + debug.setProperty("toString", handler.engine()->evaluate("1,function() { return JSON.stringify(this, 0, 2); }")); +#endif + ::callScopedHandlerObject(handler, errorValue, result); +} + +void AssetScriptingInterface::jsCallback(const QScriptValue& handler, + const QScriptValue& error, const QVariantMap& result) { + Q_ASSERT(thread() == QThread::currentThread()); + Q_ASSERT(handler.engine()); + auto engine = handler.engine(); + jsCallback(handler, error, engine->toScriptValue(result)); +} + +void AssetScriptingInterface::deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { + jsAssert(false, "TODO: deleteAsset API"); +} + +void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { + JS_ASSERT(options.isObject() || options.isString(), "expected request options Object or URL as first parameter"); + + auto decompress = options.property("decompress").toBool() || options.property("compressed").toBool(); + auto responseType = options.property("responseType").toString().toLower(); + auto url = options.property("url").toString(); + if (options.isString()) { + url = options.toString(); + } + if (responseType.isEmpty() || responseType == "string") { + responseType = "text"; + } + auto asset = AssetUtils::getATPUrl(url).path(); + auto handler = makeScopedHandlerObject(scope, callback); + + JS_ASSERT(handler.property("callback").isFunction(), + QString("Invalid callback function (%1)").arg(handler.property("callback").toVariant().typeName())); + JS_ASSERT(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset), + QString("Invalid ATP url '%1'").arg(url)); + JS_ASSERT(RESPONSE_TYPES.contains(responseType), + QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | "))); + + Promise resolved = makePromise("resolved"), + loaded = makePromise("loaded"); + + loaded->ready([=](QString error, QVariantMap result) { + qDebug() << "//loaded" << error; + jsCallback(handler, error, result); + }); + + resolved->ready([=](QString error, QVariantMap result) { + qDebug() << "//resolved" << result.value("hash"); + QString hash = result.value("hash").toString(); + if (!error.isEmpty() || !AssetUtils::isValidHash(hash)) { + loaded->reject(error.isEmpty() ? "internal hash error: " + hash : error, result); + } else { + loadAsset(hash, decompress, responseType) + ->mixin(result) + ->ready([this, loaded, hash](QString error, QVariantMap result) { + qDebug() << "//getAssetInfo/loadAsset" << error << hash; + loaded->resolve(NoError, result); + }); + } + }); + + if (AssetUtils::isValidHash(asset)) { + resolved->resolve(NoError, { { "hash", asset } }); + } else { + getAssetInfo(asset) + ->ready([this, resolved](QString error, QVariantMap result) { + qDebug() << "//getAssetInfo" << error << result.value("hash") << result.value("path"); + resolved->resolve(error, result); + }); + } +} + +void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { + const QString& URL{ "url" }; + + auto url = (options.isString() ? options : options.property(URL)).toString(); + auto asset = AssetUtils::getATPUrl(url).path(); + auto handler = makeScopedHandlerObject(scope, callback); + + JS_ASSERT(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset), + "expected options to be an asset URL or request options containing .url property"); + JS_ASSERT(handler.property("callback").isFunction(), "invalid callback function"); + getAssetInfo(asset)->ready([=](QString error, QVariantMap result) { + qDebug() << "//resolveAsset/getAssetInfo" << error << result.value("hash"); + jsCallback(handler, error, result); + }); +} + +void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { + auto data = options.property("data"); + QByteArray dataByteArray = qscriptvalue_cast(data); + auto handler = makeScopedHandlerObject(scope, callback); + auto responseType = options.property("responseType").toString().toLower(); + if (responseType.isEmpty()) { + responseType = "text"; + } + decompressBytes(dataByteArray) + ->ready([=](QString error, QVariantMap result) { + if (responseType == "arraybuffer") { + jsCallback(handler, error, result); + } else { + convertBytes(result.value("data").toByteArray(), responseType) + ->mixin(result) + ->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); + } + }); +} + +void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { + auto data = options.property("data"); + QByteArray dataByteArray = data.isString() ? + data.toString().toUtf8() : + qscriptvalue_cast(data); + auto handler = makeScopedHandlerObject(scope, callback); + auto level = options.property("level").toInt32(); + if (level < -1 || level > 9) { + level = -1; + } + compressBytes(dataByteArray, level) + ->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); +} + +void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { + auto compress = options.property("compress").toBool() || + options.property("compressed").toBool(); + auto handler = makeScopedHandlerObject(scope, callback); + auto data = options.property("data"); + auto rawPath = options.property("path").toString(); + auto path = AssetUtils::getATPUrl(rawPath).path(); + + QByteArray dataByteArray = data.isString() ? + data.toString().toUtf8() : + qscriptvalue_cast(data); + //auto rawByteLength = dataByteArray.size(); + + JS_ASSERT(path.isEmpty() || AssetUtils::isValidFilePath(path), + QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path)); + JS_ASSERT(handler.property("callback").isFunction(), + "invalid callback function"); + JS_ASSERT(dataByteArray.size() > 0, + QString("expected non-zero .data (got %1 / #%2 bytes)") + .arg(data.toVariant().typeName()) + .arg(dataByteArray.size())); + + + // [compressed] => uploaded to server => [mapped to path] + Promise prepared = makePromise("putAsset::prepared"), + uploaded = makePromise("putAsset::uploaded"), + finished = makePromise("putAsset::finished"); + + if (compress) { + qDebug() << "putAsset::compressBytes..."; + compressBytes(dataByteArray, -1) + ->finally([=](QString error, QVariantMap result) { + qDebug() << "//putAsset::compressedBytes" << error << result.keys(); + prepared->handle(error, result); + }); + } else { + prepared->resolve(NoError, {{ "data", dataByteArray }}); + } + + prepared->ready([=](QString error, QVariantMap result) { + qDebug() << "//putAsset::prepared" << error << result.value("data").toByteArray().size() << result.keys(); + uploadBytes(result.value("data").toByteArray()) + ->mixin(result) + ->ready([=](QString error, QVariantMap result) { + qDebug() << "===========//putAsset::prepared/uploadBytes" << error << result.keys(); + uploaded->handle(error, result); + }); + }); + + uploaded->ready([=](QString error, QVariantMap result) { + QString hash = result.value("hash").toString(); + qDebug() << "//putAsset::uploaded" << error << hash << result.keys(); + if (path.isEmpty()) { + finished->handle(error, result); + } else if (!AssetUtils::isValidHash(hash)) { + finished->reject("path mapping requested, but did not receive valid hash", result); + } else { + qDebug() << "symlinkAsset" << hash << path << QThread::currentThread(); + symlinkAsset(hash, path) + ->mixin(result) + ->ready([=](QString error, QVariantMap result) { + finished->handle(error, result); + qDebug() << "//symlinkAsset" << hash << path << result.keys(); + }); + } + }); + + finished->ready([=](QString error, QVariantMap result) { + qDebug() << "//putAsset::finished" << error << result.keys(); + jsCallback(handler, error, result); + }); +} diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 2812be65f9..9022316634 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -15,17 +15,21 @@ #define hifi_AssetScriptingInterface_h #include +#include #include - +#include #include +#include +#include +#include /**jsdoc * @namespace Assets */ -class AssetScriptingInterface : public QObject { +class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable { Q_OBJECT public: - AssetScriptingInterface(QScriptEngine* engine); + AssetScriptingInterface(QObject* parent = nullptr); /**jsdoc * Upload content to the connected domain's asset server. @@ -75,16 +79,37 @@ public: * @param {string} error */ Q_INVOKABLE void setMapping(QString path, QString hash, QScriptValue callback); - + Q_INVOKABLE void getMapping(QString path, QScriptValue callback); + Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); #if (PR_BUILD || DEV_BUILD) Q_INVOKABLE void sendFakedHandshake(); #endif + // Advanced APIs + // getAsset(options, scope[callback(error, result)]) -- fetches an Asset from the Server + // [options.url] an "atp:" style URL, hash, or relative mapped path to fetch + // [options.responseType] the desired reponse type (text | arraybuffer | json) + // [options.decompress] whether to apply gunzip decompression on the stream + // [scope[callback]] continuation-style (error, { responseType, data, byteLength, ... }) callback + const QStringList RESPONSE_TYPES{ "text", "arraybuffer", "json" }; + Q_INVOKABLE void getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + // putAsset(options, scope[callback(error, result)]) -- upload a new Aset to the Server + // [options.data] -- (ArrayBuffer|String) + // [options.compress] -- (true|false) + // [options.path=undefined] -- option path mapping to set on the created hash result + // [ + Q_INVOKABLE void putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void compressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + protected: - QSet _pendingRequests; - QScriptEngine* _engine; + void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QVariantMap& result); + void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QScriptValue& result); + bool jsAssert(bool condition, const QString& error); }; #endif // hifi_AssetScriptingInterface_h diff --git a/scripts/developer/tests/unit_tests/assetUnitTests.js b/scripts/developer/tests/unit_tests/assetUnitTests.js new file mode 100644 index 0000000000..be8710e50d --- /dev/null +++ b/scripts/developer/tests/unit_tests/assetUnitTests.js @@ -0,0 +1,181 @@ +/* eslint-env jasmine */ + +instrument_testrunner(true); + +describe("Assets", function () { + var context = { + definedHash: null, + definedPath: null, + definedContent: null, + }; + var NULL_HASH = new Array(64+1).join('0'); // 64 hex + var SAMPLE_FOLDER = '/assetUnitTests'; + var SAMPLE_FILE_PATH = SAMPLE_FOLDER + "/test.json"; + var SAMPLE_CONTENTS = 'Test Run on ' + JSON.stringify(Date()); + var IS_ASSET_HASH_REGEX = /^[a-f0-9]{64}$/i; + var IS_ASSET_URL = /^atp:/; + + it('Entities.canWriteAssets', function() { + expect(Entities.canWriteAssets()).toBe(true); + }); + + it('Assets.extractAssetHash(input)', function() { + // new helper method that provides a catch-all way to get at the sha256 hash + // considering the multiple, different ways ATP hash URLs are found across the + // system and in content. + + var POSITIVE_TEST_URLS = [ + 'atp:HASH', + 'atp:HASH.obj', + 'atp:HASH.fbx?cache-buster', + 'atp:/.baked/HASH/asset.fbx', + 'HASH' + ]; + var NEGATIVE_TEST_URLS = [ + 'asdf', + 'http://domain.tld', + '/path/filename.fbx', + 'atp:/path/filename.fbx?#HASH', + '' + ]; + pending(); + }); + + // new APIs + + it('Assets.getAsset(options, {callback(error, result)})', function() { pending(); }); + it('Assets.putAsset(options, {callback(error, result)})', function() { pending(); }); + it('Assets.deleteAsset(options, {callback(error, result)})', function() { pending(); }); + it('Assets.resolveAsset(options, {callback(error, result)})', function() { pending(); }); + + + // existing APIs + it('Assets.getATPUrl(input)', function() { pending(); }); + it('Assets.isValidPath(input)', function() { pending(); }); + it('Assets.isValidFilePath(input)', function() { pending(); }); + it('Assets.isValidHash(input)', function() { pending(); }); + it('Assets.setBakingEnabled(path, enabled, {callback(error)})', function() { pending(); }); + + it("Assets.uploadData(data, {callback(url, hash)})", function (done) { + Assets.uploadData(SAMPLE_CONTENTS, function(url, hash) { + expect(url).toMatch(IS_ASSET_URL); + expect(hash).toMatch(IS_ASSET_HASH_REGEX); + context.definedHash = hash; // used in later tests + context.definedContent = SAMPLE_CONTENTS; + print('context.definedHash = ' + context.definedHash); + print('context.definedContent = ' + context.definedContent); + done(); + }); + }); + + it("Assets.setMapping(path, hash {callback(error)})", function (done) { + expect(context.definedHash).toMatch(IS_ASSET_HASH_REGEX); + Assets.setMapping(SAMPLE_FILE_PATH, context.definedHash, function(error) { + if (error) error += ' ('+JSON.stringify([SAMPLE_FILE_PATH, context.definedHash])+')'; + expect(error).toBe(null); + context.definedPath = SAMPLE_FILE_PATH; + print('context.definedPath = ' + context.definedPath); + done(); + }); + }); + + it("Assets.getMapping(path, {callback(error, hash)})", function (done) { + expect(context.definedHash).toMatch(IS_ASSET_HASH_REGEX, 'asfasdf'); + expect(context.definedPath).toMatch(/^\//, 'asfasdf'); + Assets.getMapping(context.definedPath, function(error, hash) { + if (error) error += ' ('+JSON.stringify([context.definedPath])+')'; + expect(error).toBe(null); + expect(hash).toMatch(IS_ASSET_HASH_REGEX); + expect(hash).toEqual(context.definedHash); + done(); + }); + }); + + it('Assets.getMapping(nullHash, {callback(data)})', function(done) { + // FIXME: characterization test -- current behavior is that downloadData silently fails + // when given an asset that doesn't exist + Assets.downloadData(NULL_HASH, function(result) { + throw new Error("this callback 'should' not be triggered"); + }); + setTimeout(function() { done(); }, 2000); + }); + + it('Assets.downloadData(hash, {callback(data)})', function(done) { + expect(context.definedHash).toMatch(IS_ASSET_HASH_REGEX, 'asfasdf'); + Assets.downloadData('atp:' + context.definedHash, function(result) { + expect(result).toEqual(context.definedContent); + done(); + }); + }); + + it('Assets.downloadData(nullHash, {callback(data)})', function(done) { + // FIXME: characterization test -- current behavior is that downloadData silently fails + // when given an asset doesn't exist + Assets.downloadData(NULL_HASH, function(result) { + throw new Error("this callback 'should' not be triggered"); + }); + setTimeout(function() { + done(); + }, 2000); + }); + + describe('exceptions', function() { + describe('Asset.setMapping', function() { + it('-- invalid path', function(done) { + Assets.setMapping('foo', NULL_HASH, function(error/*, result*/) { + expect(error).toEqual('Path is invalid'); + /* expect(result).not.toMatch(IS_ASSET_URL); */ + done(); + }); + }); + it('-- invalid hash', function(done) { + Assets.setMapping(SAMPLE_FILE_PATH, 'bar', function(error/*, result*/) { + expect(error).toEqual('Hash is invalid'); + /* expect(result).not.toMatch(IS_ASSET_URL); */ + done(); + }); + }); + }); + describe('Asset.getMapping', function() { + it('-- invalid path throws immediate', function() { + expect(function() { + Assets.getMapping('foo', function(error, hash) { + throw new Error("should never make it here..."); + }); + throw new Error("should never make it here..."); + }).toThrowError(/invalid.*path/i); + }); + it('-- non-existing path', function(done) { + Assets.getMapping('/foo/bar/'+Date.now(), function(error, hash) { + expect(error).toEqual('Asset not found'); + expect(hash).not.toMatch(IS_ASSET_HASH_REGEX); + done(); + }); + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// this stuff allows the unit tests to be loaded indepenently and/or as part of testRunner.js execution +function run() {} +function instrument_testrunner(force) { + if (force || typeof describe === 'undefined') { + // Include testing library + Script.include('../../libraries/jasmine/jasmine.js'); + Script.include('../../libraries/jasmine/hifi-boot.js'); + + run = function() { + if (!/:console/.test(Script.resolvePath(''))) { + // invoke Script.stop (after any async tests complete) + jasmine.getEnv().addReporter({ jasmineDone: Script.stop }); + } else { + jasmine.getEnv().addReporter({ jasmineDone: function() { print("JASMINE DONE"); } }); + } + + // Run the tests + jasmine.getEnv().execute(); + }; + } +} +run(); From 61711f34ccb75bf8505e6485cc681146837212e7 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 15 Dec 2017 13:36:48 -0500 Subject: [PATCH 04/73] add support for tagging meshes with displayName --- libraries/fbx/src/FBXReader.cpp | 19 +++++++++++++++++-- libraries/model/src/model/Geometry.h | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index e4fea00a34..d7500a24c6 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1759,6 +1759,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.meshes.append(extracted.mesh); int meshIndex = geometry.meshes.size() - 1; + if (extracted.mesh._mesh) { + extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex); + } meshIDsToMeshIndices.insert(it.key(), meshIndex); } @@ -1827,7 +1830,19 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } } - + { + int i=0; + for (const auto& mesh : geometry.meshes) { + auto name = geometry.getModelNameOfMesh(i++); + if (!name.isEmpty()) { + if (mesh._mesh) { + mesh._mesh->displayName += "#" + name; + } else { + qDebug() << "modelName but no mesh._mesh" << name; + } + } + } + } return geometryPtr; } @@ -1843,7 +1858,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri reader._loadLightmaps = loadLightmaps; reader._lightmapLevel = lightmapLevel; - qDebug() << "Reading FBX: " << url; + qCDebug(modelformat) << "Reading FBX: " << url; return reader.extractFBXGeometry(mapping, url); } diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index 260de313ab..52ac7c4cd3 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -135,6 +135,8 @@ public: static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr); + QString displayName; + protected: gpu::Stream::FormatPointer _vertexFormat; From a146b778f784ce6f06ee86fdafd4f36c4c05fea5 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 15 Dec 2017 13:39:41 -0500 Subject: [PATCH 05/73] support additional .extraInfo for ray intersections --- interface/src/Application.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 3 +- interface/src/avatar/MyAvatar.cpp | 10 ++--- interface/src/raypick/RayPick.h | 3 ++ interface/src/ui/overlays/Base3DOverlay.h | 2 +- interface/src/ui/overlays/ModelOverlay.cpp | 6 +-- interface/src/ui/overlays/ModelOverlay.h | 2 +- interface/src/ui/overlays/Overlays.cpp | 6 +-- interface/src/ui/overlays/Overlays.h | 3 +- libraries/avatars/src/AvatarData.cpp | 2 + libraries/avatars/src/AvatarData.h | 1 + .../src/EntityTreeRenderer.cpp | 28 ++++++++------ .../src/RenderableModelEntityItem.cpp | 5 +-- .../src/RenderableModelEntityItem.h | 2 +- .../src/RenderablePolyVoxEntityItem.cpp | 2 +- .../src/RenderablePolyVoxEntityItem.h | 2 +- libraries/entities/src/EntityItem.h | 2 +- .../entities/src/EntityScriptingInterface.cpp | 14 +++---- .../entities/src/EntityScriptingInterface.h | 2 +- libraries/entities/src/EntityTree.cpp | 19 +++++----- libraries/entities/src/EntityTree.h | 4 +- libraries/entities/src/EntityTreeElement.cpp | 37 +++++++++--------- libraries/entities/src/EntityTreeElement.h | 10 ++--- libraries/entities/src/LightEntityItem.cpp | 2 +- libraries/entities/src/LightEntityItem.h | 2 +- libraries/entities/src/LineEntityItem.h | 2 +- libraries/entities/src/PolyLineEntityItem.h | 2 +- libraries/entities/src/PolyVoxEntityItem.h | 2 +- libraries/entities/src/ShapeEntityItem.cpp | 2 +- libraries/entities/src/ShapeEntityItem.h | 2 +- libraries/entities/src/TextEntityItem.cpp | 2 +- libraries/entities/src/TextEntityItem.h | 2 +- libraries/entities/src/WebEntityItem.cpp | 2 +- libraries/entities/src/WebEntityItem.h | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 2 +- libraries/entities/src/ZoneEntityItem.h | 2 +- libraries/render-utils/src/Model.cpp | 38 +++++++++++++++---- libraries/render-utils/src/Model.h | 4 +- libraries/script-engine/src/Mat4.cpp | 10 +++++ libraries/script-engine/src/Mat4.h | 4 ++ libraries/shared/src/GeometryUtil.cpp | 8 ++++ libraries/shared/src/GeometryUtil.h | 1 + libraries/shared/src/TriangleSet.cpp | 29 +++++++------- libraries/shared/src/TriangleSet.h | 10 +++-- 44 files changed, 181 insertions(+), 116 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f17e06cb35..2068dbbde8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1844,7 +1844,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo entityResult.distance = pickResult->distance; entityResult.surfaceNormal = pickResult->surfaceNormal; entityResult.entityID = pickResult->objectID; - entityResult.entity = DependencyManager::get()->getTree()->findEntityByID(entityResult.entityID); + entityResult.extraInfo = pickResult->extraInfo; } } return entityResult; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 8a294182bd..f481d938ed 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -546,7 +546,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic continue; } - QString extraInfo; + QVariantMap extraInfo; intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection, distance, face, surfaceNormal, extraInfo, true); @@ -554,6 +554,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic result.intersects = true; result.avatarID = avatar->getID(); result.distance = distance; + result.extraInfo = extraInfo; } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 02a1959a95..55f3e2fb36 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2401,7 +2401,6 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette }; auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { OctreeElementPointer element; - EntityItemPointer intersectedEntity = NULL; float distance; BoxFace face; const bool visibleOnly = false; @@ -2413,13 +2412,14 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette const auto lockType = Octree::Lock; // Should we refactor to take a lock just once? bool* accurateResult = NULL; - bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, - element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult); - if (!intersects || !intersectedEntity) { + QVariantMap extraInfo; + EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, + element, distance, face, normalOut, extraInfo, lockType, accurateResult); + if (entityID.isNull()) { return false; } intersectionOut = startPointIn + (directionIn * distance); - entityIdOut = intersectedEntity->getEntityItemID(); + entityIdOut = entityID; return true; }; diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 25ad4df1f3..0153ea64fc 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -29,6 +29,7 @@ public: distance = rayPickResult.distance; intersection = rayPickResult.intersection; surfaceNormal = rayPickResult.surfaceNormal; + extraInfo = rayPickResult.extraInfo; } IntersectionType type { NONE }; @@ -37,6 +38,7 @@ public: float distance { FLT_MAX }; glm::vec3 intersection { NAN }; glm::vec3 surfaceNormal { NAN }; + QVariantMap extraInfo; virtual QVariantMap toVariantMap() const override { QVariantMap toReturn; @@ -47,6 +49,7 @@ public: toReturn["intersection"] = vec3toVariant(intersection); toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal); toReturn["searchRay"] = PickResult::toVariantMap(); + toReturn["extraInfo"] = extraInfo; return toReturn; } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index d65b9850d7..df0f3c4728 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -68,7 +68,7 @@ public: BoxFace& face, glm::vec3& surfaceNormal); virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) { return findRayIntersection(origin, direction, distance, face, surfaceNormal); } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ba8bf8cbef..d283d6b302 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -445,12 +445,12 @@ QVariant ModelOverlay::getProperty(const QString& property) { bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - QString subMeshNameTemp; - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp); + QVariantMap extraInfo; + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) { return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 4f7f1e0cae..c4d2593317 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -41,7 +41,7 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) override; virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) override; + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override; virtual ModelOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 052ef0b6d8..1e2e248b2c 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -584,7 +584,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay float thisDistance; BoxFace thisFace; glm::vec3 thisSurfaceNormal; - QString thisExtraInfo; + QVariantMap thisExtraInfo; if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, thisFace, thisSurfaceNormal, thisExtraInfo)) { bool isDrawInFront = thisOverlay->getDrawInFront(); @@ -642,7 +642,7 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, obj.setProperty("face", faceName); auto intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - obj.setProperty("extraInfo", value.extraInfo); + obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -676,7 +676,7 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar value.intersection = newIntersection; } } - value.extraInfo = object["extraInfo"].toString(); + value.extraInfo = object["extraInfo"].toMap(); } bool Overlays::isLoaded(OverlayID id) { diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index abd657074c..a9f33e18b3 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -52,6 +52,7 @@ const OverlayID UNKNOWN_OVERLAY_ID = OverlayID(); * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. * @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point. * @property {Vec3} intersection - The position of the intersection point. + * @property {Object} extraInfo Additional intersection details, if available. */ class RayToOverlayIntersectionResult { public: @@ -61,7 +62,7 @@ public: BoxFace face; glm::vec3 surfaceNormal; glm::vec3 intersection; - QString extraInfo; + QVariantMap extraInfo; }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f2053e29d7..1657b921e3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2420,6 +2420,7 @@ QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, c obj.setProperty("distance", value.distance); QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -2432,6 +2433,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } + value.extraInfo = object.property("extraInfo").toVariant().toMap(); } const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d7dd2837cb..ee821ab792 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -968,6 +968,7 @@ RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {} QUuid avatarID; float distance; glm::vec3 intersection; + QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 8c3b56d89e..2814d9d06f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -668,15 +668,16 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { auto entityScriptingInterface = DependencyManager::get(); PickRay ray = _viewState->computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); - if (rayPickResult.intersects && rayPickResult.entity) { - auto properties = rayPickResult.entity->getProperties(); + EntityItemPointer entity; + if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { + auto properties = entity->getProperties(); QString urlString = properties.getHref(); QUrl url = QUrl(urlString, QUrl::StrictMode); if (url.isValid() && !url.isEmpty()){ DependencyManager::get()->handleLookupString(urlString); } - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, @@ -707,8 +708,9 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { auto entityScriptingInterface = DependencyManager::get(); PickRay ray = _viewState->computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); - if (rayPickResult.intersects && rayPickResult.entity) { - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + EntityItemPointer entity; + if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, @@ -737,10 +739,11 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { auto entityScriptingInterface = DependencyManager::get(); PickRay ray = _viewState->computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); - if (rayPickResult.intersects && rayPickResult.entity) { + EntityItemPointer entity; + if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { // qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, @@ -756,7 +759,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickReleaseOn event if (!_currentClickingOnEntityID.isInvalidID()) { - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + glm::vec2 pos2D = projectOntoEntityXYPlane( entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, @@ -781,8 +784,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { auto entityScriptingInterface = DependencyManager::get(); PickRay ray = _viewState->computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); - if (rayPickResult.intersects && rayPickResult.entity) { - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + EntityItemPointer entity; + if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, @@ -796,7 +800,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // if we were previously hovering over an entity, and this new entity is not the same as our previous entity // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, @@ -827,7 +831,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { - glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index bc99522d0f..b00fefa7aa 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -282,7 +282,7 @@ bool RenderableModelEntityItem::supportsDetailedRayIntersection() const { bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, - glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const { + glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { auto model = getModel(); if (!model) { return true; @@ -290,9 +290,8 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori // qCDebug(entitiesrenderer) << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:" // << precisionPicking; - QString extraInfo; return model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, - face, surfaceNormal, extraInfo, precisionPicking, false); + face, surfaceNormal, extraInfo, precisionPicking, false); } void RenderableModelEntityItem::getCollisionGeometryResource() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index b4f2665692..92ff1e1dd5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -70,7 +70,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setShapeType(ShapeType type) override; virtual void setCompoundShapeURL(const QString& url) override; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 356bf3a69c..d111d5741b 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -565,7 +565,7 @@ public: bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const + QVariantMap& extraInfo, bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes if (!precisionPicking) { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 6ac518f79b..7e4b4a71f7 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -55,7 +55,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setVoxelData(const QByteArray& voxelData) override; virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 088d21e84d..538a3e3a74 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -160,7 +160,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { return true; } + QVariantMap& extraInfo, bool precisionPicking) const { return true; } // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index d278283ffa..c18c4dcda8 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -778,13 +778,12 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke RayToEntityIntersectionResult result; if (_entityTree) { OctreeElementPointer element; - EntityItemPointer intersectedEntity = NULL; - result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, + result.entityID = _entityTree->findRayIntersection(ray.origin, ray.direction, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, element, result.distance, result.face, result.surfaceNormal, - (void**)&intersectedEntity, lockType, &result.accurate); - if (result.intersects && intersectedEntity) { - result.entityID = intersectedEntity->getEntityItemID(); + result.extraInfo, lockType, &result.accurate); + result.intersects = !result.entityID.isNull(); + if (result.intersects) { result.intersection = ray.origin + (ray.direction * result.distance); } } @@ -950,8 +949,7 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() : accurate(true), // assume it's accurate entityID(), distance(0), - face(), - entity(NULL) + face() { } @@ -998,6 +996,7 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); + obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -1033,6 +1032,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra if (surfaceNormal.isValid()) { vec3FromScriptValue(surfaceNormal, value.surfaceNormal); } + value.extraInfo = object.property("extraInfo").toVariant().toMap(); } bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function actor) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 095a821482..4c4e2ffbfd 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -62,7 +62,7 @@ public: BoxFace face; glm::vec3 intersection; glm::vec3 surfaceNormal; - EntityItemPointer entity; + QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToEntityIntersectionResult) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b5765bb44b..f982bbca48 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -61,8 +61,8 @@ public: float& distance; BoxFace& face; glm::vec3& surfaceNormal; - void** intersectedObject; - bool found; + QVariantMap& extraInfo; + EntityItemID entityID; }; @@ -750,23 +750,24 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData) RayArgs* args = static_cast(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); - if (entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching, + EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching, args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude, - args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->intersectedObject, args->precisionPicking)) { - args->found = true; + args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); + if (!entityID.isNull()) { + args->entityID = entityID; } return keepSearching; } -bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, intersectedObject, false }; + element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; @@ -778,7 +779,7 @@ bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& d *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate } - return args.found; + return args.entityID; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 11a747d624..8cb89d6493 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -97,11 +97,11 @@ public: virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& node, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject = NULL, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); virtual bool rootElementHasData() const override { return true; } diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 7e2958583d..2daef38f39 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -588,57 +588,60 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 return false; } -bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - void** intersectedObject, bool precisionPicking) { + QVariantMap& extraInfo, bool precisionPicking) { keepSearching = true; // assume that we will continue searching after this. + EntityItemID result; float distanceToElementCube = std::numeric_limits::max(); float distanceToElementDetails = distance; BoxFace localFace; glm::vec3 localSurfaceNormal; + QVariantMap localExtraInfo; // if the ray doesn't intersect with our cube, we can stop searching! if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) { keepSearching = false; // no point in continuing to search - return false; // we did not intersect + return result; // we did not intersect } // by default, we only allow intersections with leaves with content if (!canRayIntersect()) { - return false; // we don't intersect with non-leaves, and we keep searching + return result; // we don't intersect with non-leaves, and we keep searching } // if the distance to the element cube is not less than the current best distance, then it's not possible // for any details inside the cube to be closer so we don't need to consider them. if (_cube.contains(origin) || distanceToElementCube < distance) { - if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails, + EntityItemID entityID = findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails, face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, - intersectedObject, precisionPicking, distanceToElementCube)) { - + localExtraInfo, precisionPicking, distanceToElementCube); + if (!entityID.isNull()) { if (distanceToElementDetails < distance) { distance = distanceToElementDetails; face = localFace; surfaceNormal = localSurfaceNormal; - return true; + extraInfo = localExtraInfo; + result = entityID; } } } - return false; + return result; } -bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, +EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, - bool visibleOnly, bool collidableOnly, void** intersectedObject, bool precisionPicking, float distanceToElementCube) { + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... int entityNumber = 0; - bool somethingIntersected = false; + EntityItemID somethingIntersected; forEachEntity([&](EntityItemPointer entity) { if ( (visibleOnly && !entity->isVisible()) || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) @@ -655,6 +658,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con float localDistance; BoxFace localFace; glm::vec3 localSurfaceNormal; + QVariantMap localExtraInfo; // if the ray doesn't intersect with our cube, we can stop searching! if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace, localSurfaceNormal)) { @@ -684,14 +688,14 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con // now ask the entity if we actually intersect if (entity->supportsDetailedRayIntersection()) { if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance, - localFace, localSurfaceNormal, intersectedObject, precisionPicking)) { + localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { if (localDistance < distance) { distance = localDistance; face = localFace; surfaceNormal = localSurfaceNormal; - *intersectedObject = (void*)entity.get(); - somethingIntersected = true; + extraInfo = localExtraInfo; + somethingIntersected = entity->getEntityItemID(); } } } else { @@ -701,8 +705,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con distance = localDistance; face = localFace; surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f)); - *intersectedObject = (void*)entity.get(); - somethingIntersected = true; + somethingIntersected = entity->getEntityItemID(); } } } diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index cafae9941a..a524904c71 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -146,16 +146,16 @@ public: virtual bool deleteApproved() const override { return !hasEntities(); } virtual bool canRayIntersect() const override { return hasEntities(); } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& node, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, - const QVector& entityIdsToDiscard, bool visibleOnly = false, bool collidableOnly = false, - void** intersectedObject = NULL, bool precisionPicking = false); - virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking = false); + virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - void** intersectedObject, bool precisionPicking, float distanceToElementCube); + QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index f4944603f1..527a618f77 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -300,7 +300,7 @@ void LightEntityItem::resetLightPropertiesChanged() { bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { + QVariantMap& extraInfo, bool precisionPicking) const { // TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state // this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index edc7376079..d469368d99 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -88,7 +88,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; private: // properties of a light diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 7fda113510..9f16807084 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -63,7 +63,7 @@ class LineEntityItem : public EntityItem { virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } bool pointsChanged() const { return _pointsChanged; } void resetPointsChanged(); diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 3164a9646b..8af2b26216 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -96,7 +96,7 @@ class PolyLineEntityItem : public EntityItem { virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override { return false; } + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious! diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index d8d998b944..47d2a4b4e1 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -47,7 +47,7 @@ class PolyVoxEntityItem : public EntityItem { virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override { return false; } + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual void debugDump() const override; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 15704ebc17..79003d53b1 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -223,7 +223,7 @@ bool ShapeEntityItem::supportsDetailedRayIntersection() const { bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { + QVariantMap& extraInfo, bool precisionPicking) const { // determine the ray in the frame of the entity transformed from a unit sphere glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 20e36c88e6..308ea055e8 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -94,7 +94,7 @@ public: bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; void debugDump() const override; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 67e83ab3fd..b1a47bddbe 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -131,7 +131,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { + QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 8db929fa47..cc34803b78 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -50,7 +50,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; static const QString DEFAULT_TEXT; void setText(const QString& value); diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 5ee630d8ed..ab03c9f90c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -108,7 +108,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { + QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 9e84a3a776..3b817b6183 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -49,7 +49,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setSourceUrl(const QString& value); QString getSourceUrl() const; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 0ed523202b..0c0d29e743 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -274,7 +274,7 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) { bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { + QVariantMap& extraInfo, bool precisionPicking) const { return _zonesArePickable; } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 46e8a00c24..c864707c4c 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,7 +102,7 @@ public: virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const override; + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void debugDump() const override; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c4bc435691..2aa847fdcb 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -337,8 +337,8 @@ void Model::initJointStates() { } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) { + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; @@ -369,6 +369,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g QMutexLocker locker(&_mutex); float bestDistance = std::numeric_limits::max(); + Triangle bestModelTriangle; + Triangle bestWorldTriangle; + int bestSubMeshIndex = 0; + int subMeshIndex = 0; const FBXGeometry& geometry = getFBXGeometry(); @@ -386,8 +390,8 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g for (auto& triangleSet : _modelSpaceMeshTriangleSets) { float triangleSetDistance = 0.0f; BoxFace triangleSetFace; - glm::vec3 triangleSetNormal; - if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) { + Triangle triangleSetTriangle; + if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); @@ -397,8 +401,11 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g bestDistance = worldDistance; intersectedSomething = true; face = triangleSetFace; - surfaceNormal = glm::vec3(meshToWorldMatrix * glm::vec4(triangleSetNormal, 0.0f)); - extraInfo = geometry.getModelNameOfMesh(subMeshIndex); + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + extraInfo["_worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["_meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + bestSubMeshIndex = subMeshIndex; } } subMeshIndex++; @@ -406,9 +413,24 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g if (intersectedSomething) { distance = bestDistance; + surfaceNormal = bestWorldTriangle.getNormal(); + if (pickAgainstTriangles) { + extraInfo["subMeshIndex"] = bestSubMeshIndex; + extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); + extraInfo["subMeshTriangleWorld"] = QVariantMap{ + { "v0", vec3toVariant(bestWorldTriangle.v0) }, + { "v1", vec3toVariant(bestWorldTriangle.v1) }, + { "v2", vec3toVariant(bestWorldTriangle.v2) }, + }; + extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal()); + extraInfo["subMeshTriangle"] = QVariantMap{ + { "v0", vec3toVariant(bestModelTriangle.v0) }, + { "v1", vec3toVariant(bestModelTriangle.v1) }, + { "v2", vec3toVariant(bestModelTriangle.v2) }, + }; + } + } - - return intersectedSomething; } return intersectedSomething; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 7568a17342..30dc315484 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -158,8 +158,8 @@ public: void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } diff --git a/libraries/script-engine/src/Mat4.cpp b/libraries/script-engine/src/Mat4.cpp index 6965f43b32..15015782e2 100644 --- a/libraries/script-engine/src/Mat4.cpp +++ b/libraries/script-engine/src/Mat4.cpp @@ -11,7 +11,9 @@ #include #include +#include #include +#include #include "ScriptEngineLogging.h" #include "ScriptEngine.h" #include "Mat4.h" @@ -32,6 +34,14 @@ glm::mat4 Mat4::createFromColumns(const glm::vec4& col0, const glm::vec4& col1, return glm::mat4(col0, col1, col2, col3); } +glm::mat4 Mat4::createFromArray(const QVector& floats) const { + if (floats.size() != 16 && floats.size() != 9) { + context()->throwError("createFromVector requires 16 floats for mat4 (or 9 if providing a mat3)"); + return glm::mat4(); + } + return floats.size() == 9 ? glm::mat4(glm::make_mat3(floats.constData())) : glm::make_mat4(floats.constData()); +} + glm::vec3 Mat4::extractTranslation(const glm::mat4& m) const { return ::extractTranslation(m); } diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 8b942874ee..ceeea3ccec 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include "RegisteredMetaTypes.h" /// Scriptable Mat4 object. Used exclusively in the JavaScript API class Mat4 : public QObject, protected QScriptable { @@ -28,6 +31,7 @@ public slots: glm::mat4 createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const; glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const; glm::mat4 createFromColumns(const glm::vec4& col0, const glm::vec4& col1, const glm::vec4& col2, const glm::vec4& col3) const; + glm::mat4 createFromArray(const QVector& floats) const; glm::vec3 extractTranslation(const glm::mat4& m) const; glm::quat extractRotation(const glm::mat4& m) const; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 956c61deaf..0742a5625b 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -292,6 +292,14 @@ glm::vec3 Triangle::getNormal() const { return glm::normalize(glm::cross(edge1, edge2)); } +Triangle Triangle::operator*(const glm::mat4& transform) const { + return { + glm::vec3(transform * glm::vec4(v0, 1.0f)), + glm::vec3(transform * glm::vec4(v1, 1.0f)), + glm::vec3(transform * glm::vec4(v2, 1.0f)) + }; +} + bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { glm::vec3 firstSide = v0 - v1; diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index dcb90643b6..4832616fbd 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -104,6 +104,7 @@ public: glm::vec3 v1; glm::vec3 v2; glm::vec3 getNormal() const; + Triangle operator*(const glm::mat4& transform) const; }; inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index ce7dd18921..3f8f748720 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -31,7 +31,7 @@ void TriangleSet::clear() { } bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) { + float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { // reset our distance to be the max possible, lower level tests will store best distance here distance = std::numeric_limits::max(); @@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& } int trianglesTouched = 0; - auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface); + auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, triangle, precision, trianglesTouched, allowBackface); #if WANT_DEBUGGING if (precision) { @@ -95,11 +95,12 @@ void TriangleSet::balanceOctree() { // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; float boxDistance = distance; float bestDistance = distance; + glm::vec3 surfaceNormal; if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { @@ -112,14 +113,14 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec if (precision) { for (const auto& triangleIndex : _triangleIndices) { - const auto& triangle = _allTriangles[triangleIndex]; + const auto& thisTriangle = _allTriangles[triangleIndex]; float thisTriangleDistance; trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) { + if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; intersectedSomething = true; - surfaceNormal = triangle.getNormal(); + triangle = thisTriangle; distance = bestDistance; } } @@ -204,7 +205,8 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { } bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect @@ -212,6 +214,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi float bestLocalDistance = distance; BoxFace bestLocalFace; + Triangle bestLocalTriangle; glm::vec3 bestLocalNormal; bool intersects = false; @@ -229,7 +232,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi float childDistance = distance; BoxFace childFace; - glm::vec3 childNormal; + Triangle childTriangle; // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { @@ -237,22 +240,22 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi // check each child, if there's an intersection, it will return some distance that we need // to compare against the other results, because there might be multiple intersections and // we will always choose the best (shortest) intersection - if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; bestLocalFace = childFace; - bestLocalNormal = childNormal; + bestLocalTriangle = childTriangle; intersects = true; } } } } // also check our local triangle set - if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) { + if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; bestLocalFace = childFace; - bestLocalNormal = childNormal; + bestLocalTriangle = childTriangle; intersects = true; } } @@ -260,7 +263,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi if (intersects) { distance = bestLocalDistance; face = bestLocalFace; - surfaceNormal = bestLocalNormal; + triangle = bestLocalTriangle; } return intersects; } diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 3b0b33d7d5..786f58804f 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -27,7 +27,8 @@ class TriangleSet { void clear(); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); const AABox& getBounds() const { return _bounds; } @@ -38,7 +39,8 @@ class TriangleSet { // checks our internal list of triangles bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); std::vector& _allTriangles; std::map _children; @@ -60,7 +62,7 @@ public: void insert(const Triangle& t); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false); + float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); void balanceOctree(); @@ -72,7 +74,7 @@ public: // intersection occurs, the distance and surface normal will be provided. // note: this might side-effect internal structures bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched); // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a From 77874de9093bebd98377dd4d8e8400ce32adce1f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 21 Dec 2017 15:05:23 -0800 Subject: [PATCH 06/73] fix spacing --- interface/resources/qml/js/Utils.jsc | Bin 6596 -> 6548 bytes .../src/RenderableModelEntityItem.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc index ab20e996b9469915ac6a89901da175143e6b5024..96c5b26fda10a505b491ad2d08588089fdfa8922 100644 GIT binary patch delta 581 zcmZ9JO=uHQ5Xb+s*~m5+UsBqp_2OZ35G~|MD|kpTN+=slRs3o$N>=|N=0 zAPu=Rj9`vEh^H2mEhyfUf;|XUP*9NCn-)BT2!bCNdC7{_cl`b4&HR~J8M-qxc;(vg zg$cW4hXGKRA8%ITi{}VP`hohbqPc!==JlT3kIKuX+o{@MnU2>Uj%zSDKxb%!Z5j_G zf@r_xZTyTz3EZVU3ct{S_d4)d2Y%|nu=`58OfFUK-`zxovLb9HIYqKlF|BDwQsC?!b5q5 z=C`0}h32-PS%sW`k>dbJRm#!XR;%R+ZUp<&oxaYzr}KYxJA+gAMD~BN;E7B}9D06O z@~It9`xkXJC>gC=k2{M4a+WAG_<^t}&EJINyP}uw^O88CJ@N5(5!?T#v~f_Ct~N#J zT~lP%bC$TVmfrVCwOeS4(3j2CU+wg2*iN1@O!s4Gnn-e{+m3jF)l~ICElJh6Yu#DW u0N&d(r^z563z+=2=Kx7J+`CDNOc^HciU^W!Eb^87QXQOcZtvs7K>0Vt9l7@a delta 637 zcmbPYe8gC%u*@VmC9xz?kb!}Lfs>VC&A*2iV;C72bXXY}mb_f{tI)LMb*EnX9J{G= zWhd&iFd9tU7|!&7eKHqg7~_G-iHzY~3cMh#3=G`tlMgYvF&a$fWZEvcg@u9P2nz$l z6&41DCoBvMGMg_lWiv8bOtxi_WOruBXGmd4om|Nh%*?>RF!?k~?dAYhTV_U!&3zpA z85u1$=W*FErx|#5GkJ77BzShZsLb%~7W3^6f(m7TgeIT}6@Y{~P=qQ#LJi154Gjzo z44$1WDi#0#|A$IU7T}ZcftcGc0VHq)#(>(a=Zul6!06AyDCw_rXFZq!SU|;|l@WHnm>~<-TO!FIsU6ZGCNN?UCPymWD zMWIA-XgGk}2r>s0_8`A*o+*^TI7y_L$%1!sK9}<36(Sal7MmZ6ykrE4^m8arelNz) YXtDXf*f&O23s6FtypTt1vVdd@0Nd!%PXGV_ diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index cfce275008..0b8c1a5bc5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1255,7 +1255,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _currentTextures = newTextures; } } - if(entity->_needsJointSimulation) { + if (entity->_needsJointSimulation) { entity->copyAnimationJointDataToModel(); } entity->updateModelBounds(); From e94103633705d80c7095bd298b5e3703a5a5f9d9 Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 2 Jan 2018 15:28:01 -0500 Subject: [PATCH 07/73] renames per CR feedback / code cleanup --- libraries/shared/src/shared/MiniPromises.cpp | 4 +- libraries/shared/src/shared/MiniPromises.h | 203 +++++++++---------- 2 files changed, 99 insertions(+), 108 deletions(-) diff --git a/libraries/shared/src/shared/MiniPromises.cpp b/libraries/shared/src/shared/MiniPromises.cpp index fdfb509608..28e2c18857 100644 --- a/libraries/shared/src/shared/MiniPromises.cpp +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -1,4 +1,2 @@ #include "MiniPromises.h" -namespace { - int promiseMetaTypeId = qRegisterMetaType("MiniPromise::Promise"); -} +int MiniPromise::metaTypeID = qRegisterMetaType("MiniPromise::Promise"); diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h index 17f2394a2f..c5d21a22fb 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -1,19 +1,19 @@ #pragma once -// Minimalist threadsafe Promise-like helper for instrumenting asynchronous results +// Minimalist threadsafe Promise-like helper for managing asynchronous results // -// This class pivots around composable continuation-style callback handlers: +// This class pivots around continuation-style callback handlers: // auto successCallback = [=](QVariantMap result) { .... } // auto errorCallback = [=](QString error) { .... } // auto combinedCallback = [=](QString error, QVariantMap result) { .... } // // * Callback Handlers are automatically invoked on the right thread (the Promise's thread). // * Callbacks can be assigned anytime during a Promise's life and "do the right thing". -// - ie: for code clarity you can define success cases first (or maintain time order) +// - ie: for code clarity you can define success cases first or choose to maintain time order // * "Deferred" concept can be used to publish outcomes. // * "Promise" concept be used to subscribe to outcomes. // -// See AssetScriptingInterface.cpp for some examples of using to simplify chained async result. +// See AssetScriptingInterface.cpp for some examples of using to simplify chained async results. #include #include @@ -25,35 +25,37 @@ class MiniPromise : public QObject, public std::enable_shared_from_this, public ReadWriteLockable { Q_OBJECT public: - using handlerFunction = std::function; - using successFunction = std::function; - using errorFunction = std::function; - using handlers = QVector; + using HandlerFunction = std::function; + using SuccessFunction = std::function; + using ErrorFunction = std::function; + using HandlerFunctions = QVector; using Promise = std::shared_ptr; - MiniPromise(const QString debugName) { setObjectName(debugName); } + + static int metaTypeID; + MiniPromise() {} + MiniPromise(const QString debugName) { setObjectName(debugName); } + ~MiniPromise() { - qDebug() << "~MiniPromise" << objectName(); if (!_rejected && !_resolved) { - qWarning() << "====== WARNING: unhandled MiniPromise" << objectName() << _error << _result; + qWarning() << "MiniPromise::~MiniPromise -- destroying unhandled promise:" << objectName() << _error << _result; + } + } + Promise self() { return shared_from_this(); } + + Q_INVOKABLE void executeOnPromiseThread(std::function function) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod( + this, "executeOnPromiseThread", Qt::BlockingQueuedConnection, + Q_ARG(std::function, function)); + } else { + function(); } } - QString _error; - QVariantMap _result; - std::atomic _rejected{false}; - std::atomic _resolved{false}; - handlers _onresolve; - handlers _onreject; - handlers _onfinally; - - Promise self() { return shared_from_this(); } - // result aggregation helpers -- eg: deferred->defaults(interimResultMap)->ready(...) - // copy values from the input map, but only for keys that don't already exist Promise mixin(const QVariantMap& source) { - qDebug() << objectName() << "mixin"; withWriteLock([&]{ for (const auto& key : source.keys()) { if (!_result.contains(key)) { @@ -65,7 +67,6 @@ public: } // copy values from the input map, replacing any existing keys Promise assignResult(const QVariantMap& source) { - qDebug() << objectName() << "assignResult"; withWriteLock([&]{ for (const auto& key : source.keys()) { _result[key] = source[key]; @@ -74,15 +75,15 @@ public: return self(); } - // TODO: I think calling as "ready" makes it read better, but is customary Promise "finally" sufficient? - Promise ready(handlerFunction always) { return finally(always); } - Promise finally(handlerFunction always) { + // callback registration methods + Promise ready(HandlerFunction always) { return finally(always); } + Promise finally(HandlerFunction always) { if (!_rejected && !_resolved) { withWriteLock([&]{ _onfinally << always; }); } else { - qDebug() << "finally (already resolved/rejected)" << objectName(); + qWarning() << "MiniPromise::finally() called after promise was already rejected or resolved:" << objectName(); executeOnPromiseThread([&]{ withReadLock([&]{ always(_error, _result); @@ -91,20 +92,20 @@ public: } return self(); } - Promise fail(errorFunction errorOnly) { + Promise fail(ErrorFunction errorOnly) { return fail([this, errorOnly](QString error, QVariantMap result) { errorOnly(error); }); } - Promise fail(handlerFunction failFunc) { + Promise fail(HandlerFunction failFunc) { if (!_rejected) { withWriteLock([&]{ _onreject << failFunc; }); } else { executeOnPromiseThread([&]{ - qDebug() << "fail (already rejected)" << objectName(); + qWarning() << "MiniPromise::fail() called after promise was already rejected:" << objectName(); withReadLock([&]{ failFunc(_error, _result); }); @@ -113,19 +114,19 @@ public: return self(); } - Promise then(successFunction successOnly) { + Promise then(SuccessFunction successOnly) { return then([this, successOnly](QString error, QVariantMap result) { successOnly(result); }); } - Promise then(handlerFunction successFunc) { + Promise then(HandlerFunction successFunc) { if (!_resolved) { withWriteLock([&]{ _onresolve << successFunc; }); } else { executeOnPromiseThread([&]{ - qDebug() << "fail (already resolved)" << objectName(); + qWarning() << "MiniPromise::then() called after promise was already resolved:" << objectName(); withReadLock([&]{ successFunc(_error, _result); }); @@ -133,9 +134,9 @@ public: } return self(); } - // register combined success/error handlers - Promise then(successFunction successOnly, errorFunction errorOnly) { - // note: first arg can be null (see ES6 .then(null, errorHandler) conventions) + + // NOTE: first arg may be null (see ES6 .then(null, errorHandler) conventions) + Promise then(SuccessFunction successOnly, ErrorFunction errorOnly) { if (successOnly) { then(successOnly); } @@ -146,8 +147,8 @@ public: } // trigger methods + // handle() automatically resolves or rejects the promise (based on whether an error value occurred) Promise handle(QString error, const QVariantMap& result) { - qDebug() << "handle" << objectName() << error; if (error.isEmpty()) { resolve(error, result); } else { @@ -155,23 +156,64 @@ public: } return self(); } + Promise resolve(QVariantMap result) { return resolve(QString(), result); } + Promise resolve(QString error, const QVariantMap& result) { + setState(true, error, result); - Q_INVOKABLE void executeOnPromiseThread(std::function function) { - if (QThread::currentThread() != thread()) { - qDebug() << "-0-0-00-0--0" << objectName() << "executeOnPromiseThread -- wrong thread" << QThread::currentThread(); - QMetaObject::invokeMethod( - this, "executeOnPromiseThread", Qt::BlockingQueuedConnection, - Q_ARG(std::function, function)); - } else { - function(); - } + QString localError; + QVariantMap localResult; + HandlerFunctions resolveHandlers; + HandlerFunctions finallyHandlers; + withReadLock([&]{ + localError = _error; + localResult = _result; + resolveHandlers = _onresolve; + finallyHandlers = _onfinally; + }); + executeOnPromiseThread([&]{ + for (const auto& onresolve : resolveHandlers) { + onresolve(localError, localResult); + } + for (const auto& onfinally : finallyHandlers) { + onfinally(localError, localResult); + } + }); + return self(); } + Promise reject(QString error) { + return reject(error, QVariantMap()); + } + Promise reject(QString error, const QVariantMap& result) { + setState(false, error, result); + + QString localError; + QVariantMap localResult; + HandlerFunctions rejectHandlers; + HandlerFunctions finallyHandlers; + withReadLock([&]{ + localError = _error; + localResult = _result; + rejectHandlers = _onreject; + finallyHandlers = _onfinally; + }); + executeOnPromiseThread([&]{ + for (const auto& onreject : rejectHandlers) { + onreject(localError, localResult); + } + for (const auto& onfinally : finallyHandlers) { + onfinally(localError, localResult); + } + }); + return self(); + } + +private: + Promise setState(bool resolved, QString error, const QVariantMap& result) { - qDebug() << "setState" << objectName() << resolved << error; if (resolved) { _resolved = true; } else { @@ -181,65 +223,16 @@ public: _error = error; }); assignResult(result); - qDebug() << "//setState" << objectName() << resolved << error; - return self(); - } - Promise resolve(QString error, const QVariantMap& result) { - setState(true, error, result); - qDebug() << "handle" << objectName() << error; - { - QString error; - QVariantMap result; - handlers toresolve; - handlers tofinally; - withReadLock([&]{ - error = _error; - result = _result; - toresolve = _onresolve; - tofinally = _onfinally; - }); - executeOnPromiseThread([&]{ - for (const auto& onresolve : toresolve) { - onresolve(error, result); - } - for (const auto& onfinally : tofinally) { - onfinally(error, result); - } - }); - } - return self(); - } - Promise reject(QString error) { - return reject(error, QVariantMap()); - } - Promise reject(QString error, const QVariantMap& result) { - setState(false, error, result); - qDebug() << "handle" << objectName() << error; - { - QString error; - QVariantMap result; - handlers toreject; - handlers tofinally; - withReadLock([&]{ - error = _error; - result = _result; - toreject = _onreject; - tofinally = _onfinally; - }); - executeOnPromiseThread([&]{ - for (const auto& onreject : toreject) { - onreject(error, result); - } - for (const auto& onfinally : tofinally) { - onfinally(error, result); - } - if (toreject.isEmpty() && tofinally.isEmpty()) { - qWarning() << "WARNING: unhandled MiniPromise::reject" << objectName() << error << result; - } - }); - } return self(); } + + QString _error; + QVariantMap _result; + std::atomic _rejected{false}; + std::atomic _resolved{false}; + HandlerFunctions _onresolve; + HandlerFunctions _onreject; + HandlerFunctions _onfinally; }; inline MiniPromise::Promise makePromise(const QString& hint = QString()) { From 8d2e04167832be70e90f54926d909d30e7784389 Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 3 Jan 2018 03:39:24 -0500 Subject: [PATCH 08/73] remove debug prints; CR feedback --- .../src/BaseAssetScriptingInterface.cpp | 16 +- .../src/AssetScriptingInterface.cpp | 139 +++++++++--------- 2 files changed, 70 insertions(+), 85 deletions(-) diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index bfdc21c7d5..8172f12c79 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -36,8 +36,6 @@ BaseAssetScriptingInterface::BaseAssetScriptingInterface(QObject* parent) : QObj bool BaseAssetScriptingInterface::initializeCache() { - qDebug() << "BaseAssetScriptingInterface::getCacheStatus -- current values" << _cache.cacheDirectory() << _cache.cacheSize() << _cache.maximumCacheSize(); - // NOTE: *instances* of QNetworkDiskCache are not thread-safe -- however, different threads can effectively // use the same underlying cache if configured with identical settings. Once AssetClient's disk cache settings // become available we configure our instance to match. @@ -50,29 +48,23 @@ bool BaseAssetScriptingInterface::initializeCache() { } // attempt to initialize the cache - qDebug() << "BaseAssetScriptingInterface::getCacheStatus -- invoking AssetClient::init" << assetClient().data(); QMetaObject::invokeMethod(assetClient().data(), "init"); - qDebug() << "BaseAssetScriptingInterface::getCacheStatus querying cache status" << QThread::currentThread(); Promise deferred = makePromise("BaseAssetScriptingInterface--queryCacheStatus"); deferred->then([&](QVariantMap result) { - qDebug() << "//queryCacheStatus" << QThread::currentThread(); auto cacheDirectory = result.value("cacheDirectory").toString(); - auto cacheSize = result.value("cacheSize").toLongLong(); auto maximumCacheSize = result.value("maximumCacheSize").toLongLong(); - qDebug() << "///queryCacheStatus" << cacheDirectory << cacheSize << maximumCacheSize; _cache.setCacheDirectory(cacheDirectory); _cache.setMaximumCacheSize(maximumCacheSize); }); deferred->fail([&](QString error) { - qDebug() << "//queryCacheStatus ERROR" << QThread::currentThread() << error; + qDebug() << "BaseAssetScriptingInterface::queryCacheStatus ERROR" << QThread::currentThread() << error; }); assets->cacheInfoRequest(deferred); return false; // cache is not ready yet } QVariantMap BaseAssetScriptingInterface::getCacheStatus() { - //assetClient()->cacheInfoRequest(this, "onCacheInfoResponse"); return { { "cacheDirectory", _cache.cacheDirectory() }, { "cacheSize", _cache.cacheSize() }, @@ -104,7 +96,6 @@ QVariantMap BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) { } QVariantMap BaseAssetScriptingInterface::loadFromCache(const QUrl& url) { - qDebug() << "loadFromCache" << url; QVariantMap result = { { "metadata", queryCacheMeta(url) }, { "data", QByteArray() }, @@ -112,7 +103,6 @@ QVariantMap BaseAssetScriptingInterface::loadFromCache(const QUrl& url) { // caller is responsible for the deletion of the ioDevice, hence the unique_ptr if (auto ioDevice = std::unique_ptr(_cache.data(url))) { QByteArray data = ioDevice->readAll(); - qCDebug(asset_client) << url.toDisplayString() << "loaded from disk cache (" << data.size() << " bytes)"; result["data"] = data; } return result; @@ -170,7 +160,6 @@ Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, Q Q_ASSERT(thread() == QThread::currentThread()); fetched->mixin(result); if (decompress) { - qDebug() << "loadAsset::decompressBytes..."; decompressBytes(result.value("data").toByteArray()) ->mixin(result) ->ready([=](QString error, QVariantMap result) { @@ -182,7 +171,6 @@ Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, Q }); fetched->ready([=](QString error, QVariantMap result) { - qDebug() << "loadAsset::fetched" << error; if (responseType == "arraybuffer") { loaded->resolve(NoError, result); } else { @@ -268,7 +256,6 @@ Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { Promise deferred = makePromise(__FUNCTION__); QObject::connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { - qDebug() << "...BaseAssetScriptingInterface::downloadBytes" << request->getErrorString(); // note: we are now on the "Resource Manager" thread Q_ASSERT(QThread::currentThread() == request->thread()); Q_ASSERT(request->getState() == AssetRequest::Finished); @@ -287,7 +274,6 @@ Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { error = request->getError(); result = { { "error", request->getError() } }; } - qDebug() << "//BaseAssetScriptingInterface::downloadBytes" << error << result.keys(); // forward thread-safe copies back to our thread deferred->handle(error, result); request->deleteLater(); diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index b5492db304..a7d0bad3c2 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -42,12 +42,12 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { QByteArray dataByteArray = data.toUtf8(); auto upload = DependencyManager::get()->createUpload(dataByteArray); - Promise deferred = makePromise(__FUNCTION__) - ->ready([this, handler](QString error, QVariantMap result) { - auto url = result.value("url").toString(); - auto hash = result.value("hash").toString(); - jsCallback(handler, url, hash); - }); + Promise deferred = makePromise(__FUNCTION__); + deferred->ready([this, handler](QString error, QVariantMap result) { + auto url = result.value("url").toString(); + auto hash = result.value("hash").toString(); + jsCallback(handler, url, hash); + }); connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) { // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly) @@ -64,10 +64,10 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) { auto handler = makeScopedHandlerObject(thisObject(), callback); auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash); - Promise deferred = makePromise(__FUNCTION__) - ->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); + Promise deferred = makePromise(__FUNCTION__); + deferred->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [this, deferred](SetMappingRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); @@ -96,11 +96,11 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb auto assetClient = DependencyManager::get(); auto assetRequest = assetClient->createRequest(hash); - Promise deferred = makePromise(__FUNCTION__) - ->ready([=](QString error, QVariantMap result) { - // FIXME: to remain backwards-compatible the signature here is "callback(data, n/a)" - jsCallback(handler, result.value("data").toString(), { { "errorMessage", error } }); - }); + Promise deferred = makePromise(__FUNCTION__); + deferred->ready([=](QString error, QVariantMap result) { + // FIXME: to remain backwards-compatible the signature here is "callback(data, n/a)" + jsCallback(handler, result.value("data").toString(), { { "errorMessage", error } }); + }); connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); @@ -127,10 +127,10 @@ void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScri auto handler = makeScopedHandlerObject(thisObject(), callback); auto setBakingEnabledRequest = DependencyManager::get()->createSetBakingEnabledRequest({ path }, enabled); - Promise deferred = makePromise(__FUNCTION__) - ->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); + Promise deferred = makePromise(__FUNCTION__); + deferred->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); @@ -160,14 +160,13 @@ void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) { JS_ASSERT(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")"); JS_ASSERT(callback.isFunction(), "expected second parameter to be a callback function"); qDebug() << ">>getMapping//getAssetInfo" << path; - getAssetInfo(path)->ready([this, handler](QString error, QVariantMap result) { + Promise promise = getAssetInfo(path); + promise->ready([this, handler](QString error, QVariantMap result) { qDebug() << "//getMapping//getAssetInfo" << error << result.keys(); jsCallback(handler, error, result.value("hash").toString()); }); } -/////////////////////////// new APIS //////////////////////////////////// - bool AssetScriptingInterface::jsAssert(bool condition, const QString& error) { if (condition) { return true; @@ -187,10 +186,6 @@ void AssetScriptingInterface::jsCallback(const QScriptValue& handler, JS_ASSERT(handler.isObject() && handler.property("callback").isFunction(), QString("jsCallback -- .callback is not a function (%1)") .arg(handler.property("callback").toVariant().typeName())); -#if 1 || DEGUG_JSCALLBACK - QScriptValue debug = result; - debug.setProperty("toString", handler.engine()->evaluate("1,function() { return JSON.stringify(this, 0, 2); }")); -#endif ::callScopedHandlerObject(handler, errorValue, result); } @@ -228,8 +223,8 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, JS_ASSERT(RESPONSE_TYPES.contains(responseType), QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | "))); - Promise resolved = makePromise("resolved"), - loaded = makePromise("loaded"); + Promise resolved = makePromise("resolved"); + Promise loaded = makePromise("loaded"); loaded->ready([=](QString error, QVariantMap result) { qDebug() << "//loaded" << error; @@ -242,9 +237,9 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, if (!error.isEmpty() || !AssetUtils::isValidHash(hash)) { loaded->reject(error.isEmpty() ? "internal hash error: " + hash : error, result); } else { - loadAsset(hash, decompress, responseType) - ->mixin(result) - ->ready([this, loaded, hash](QString error, QVariantMap result) { + Promise promise = loadAsset(hash, decompress, responseType); + promise->mixin(result); + promise->ready([this, loaded, hash](QString error, QVariantMap result) { qDebug() << "//getAssetInfo/loadAsset" << error << hash; loaded->resolve(NoError, result); }); @@ -254,11 +249,11 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, if (AssetUtils::isValidHash(asset)) { resolved->resolve(NoError, { { "hash", asset } }); } else { - getAssetInfo(asset) - ->ready([this, resolved](QString error, QVariantMap result) { - qDebug() << "//getAssetInfo" << error << result.value("hash") << result.value("path"); + Promise promise = getAssetInfo(asset); + promise->ready([this, resolved](QString error, QVariantMap result) { + qDebug() << "//getAssetInfo" << error << result.value("hash") << result.value("path"); resolved->resolve(error, result); - }); + }); } } @@ -286,34 +281,40 @@ void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue if (responseType.isEmpty()) { responseType = "text"; } - decompressBytes(dataByteArray) - ->ready([=](QString error, QVariantMap result) { - if (responseType == "arraybuffer") { + Promise promise = decompressBytes(dataByteArray); + promise->ready([=](QString error, QVariantMap result) { + if (responseType == "arraybuffer") { + jsCallback(handler, error, result); + } else { + Promise promise = convertBytes(result.value("data").toByteArray(), responseType); + promise->mixin(result); + promise->ready([=](QString error, QVariantMap result) { jsCallback(handler, error, result); - } else { - convertBytes(result.value("data").toByteArray(), responseType) - ->mixin(result) - ->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); - } + }); + } }); } +namespace { + const int32_t DEFAULT_GZIP_COMPRESSION_LEVEL = -1; + const int32_t MAX_GZIP_COMPRESSION_LEVEL = 9; +} + void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { + auto data = options.property("data"); QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast(data); auto handler = makeScopedHandlerObject(scope, callback); auto level = options.property("level").toInt32(); - if (level < -1 || level > 9) { - level = -1; + if (level < DEFAULT_GZIP_COMPRESSION_LEVEL || level > MAX_GZIP_COMPRESSION_LEVEL) { + level = DEFAULT_GZIP_COMPRESSION_LEVEL; } - compressBytes(dataByteArray, level) - ->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); + Promise promise = compressBytes(dataByteArray, level); + promise->ready([=](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); } void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { @@ -327,7 +328,6 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast(data); - //auto rawByteLength = dataByteArray.size(); JS_ASSERT(path.isEmpty() || AssetUtils::isValidFilePath(path), QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path)); @@ -338,18 +338,17 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, .arg(data.toVariant().typeName()) .arg(dataByteArray.size())); - // [compressed] => uploaded to server => [mapped to path] - Promise prepared = makePromise("putAsset::prepared"), - uploaded = makePromise("putAsset::uploaded"), - finished = makePromise("putAsset::finished"); + Promise prepared = makePromise("putAsset::prepared"); + Promise uploaded = makePromise("putAsset::uploaded"); + Promise finished = makePromise("putAsset::finished"); if (compress) { qDebug() << "putAsset::compressBytes..."; - compressBytes(dataByteArray, -1) - ->finally([=](QString error, QVariantMap result) { - qDebug() << "//putAsset::compressedBytes" << error << result.keys(); - prepared->handle(error, result); + Promise promise = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL); + promise->finally([=](QString error, QVariantMap result) { + qDebug() << "//putAsset::compressedBytes" << error << result.keys(); + prepared->handle(error, result); }); } else { prepared->resolve(NoError, {{ "data", dataByteArray }}); @@ -357,12 +356,12 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, prepared->ready([=](QString error, QVariantMap result) { qDebug() << "//putAsset::prepared" << error << result.value("data").toByteArray().size() << result.keys(); - uploadBytes(result.value("data").toByteArray()) - ->mixin(result) - ->ready([=](QString error, QVariantMap result) { - qDebug() << "===========//putAsset::prepared/uploadBytes" << error << result.keys(); - uploaded->handle(error, result); - }); + Promise promise = uploadBytes(result.value("data").toByteArray()); + promise->mixin(result); + promise->ready([=](QString error, QVariantMap result) { + qDebug() << "===========//putAsset::prepared/uploadBytes" << error << result.keys(); + uploaded->handle(error, result); + }); }); uploaded->ready([=](QString error, QVariantMap result) { @@ -374,10 +373,10 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, finished->reject("path mapping requested, but did not receive valid hash", result); } else { qDebug() << "symlinkAsset" << hash << path << QThread::currentThread(); - symlinkAsset(hash, path) - ->mixin(result) - ->ready([=](QString error, QVariantMap result) { - finished->handle(error, result); + Promise promise = symlinkAsset(hash, path); + promise->mixin(result); + promise->ready([=](QString error, QVariantMap result) { + finished->handle(error, result); qDebug() << "//symlinkAsset" << hash << path << result.keys(); }); } From a863aec4a031fe7bd3f60501448baa1546542226 Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 3 Jan 2018 04:01:43 -0500 Subject: [PATCH 09/73] put existing AssetUtils functions in namespace per CR feedback --- assignment-client/src/assets/AssetServer.h | 1 + assignment-client/src/assets/BakeAssetTask.h | 2 ++ assignment-client/src/assets/UploadAssetTask.cpp | 5 +++-- libraries/networking/src/AssetClient.cpp | 14 ++++++++------ libraries/networking/src/AssetClient.h | 2 ++ libraries/networking/src/AssetRequest.cpp | 8 ++++---- libraries/networking/src/AssetRequest.h | 2 +- libraries/networking/src/AssetUpload.cpp | 4 ++-- libraries/networking/src/AssetUtils.cpp | 4 ++++ libraries/networking/src/AssetUtils.h | 16 +++------------- 10 files changed, 30 insertions(+), 28 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index e6393e6a98..7db37860b9 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -21,6 +21,7 @@ #include "AssetUtils.h" #include "ReceivedMessage.h" +using namespace AssetUtils; namespace std { template <> diff --git a/assignment-client/src/assets/BakeAssetTask.h b/assignment-client/src/assets/BakeAssetTask.h index c73a8bff65..8fe93a85e3 100644 --- a/assignment-client/src/assets/BakeAssetTask.h +++ b/assignment-client/src/assets/BakeAssetTask.h @@ -22,6 +22,8 @@ #include +using namespace AssetUtils; + class BakeAssetTask : public QObject, public QRunnable { Q_OBJECT public: diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 5e6d59d032..238572c451 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -20,6 +20,7 @@ #include "ClientServerUtils.h" +using namespace AssetUtils; UploadAssetTask::UploadAssetTask(QSharedPointer receivedMessage, SharedNodePointer senderNode, const QDir& resourcesDir, uint64_t filesizeLimit) : @@ -54,7 +55,7 @@ void UploadAssetTask::run() { } else { QByteArray fileData = buffer.read(fileSize); - auto hash = hashData(fileData); + auto hash = AssetUtils::hashData(fileData); auto hexHash = hash.toHex(); qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()) @@ -66,7 +67,7 @@ void UploadAssetTask::run() { if (file.exists()) { // check if the local file has the correct contents, otherwise we overwrite - if (file.open(QIODevice::ReadOnly) && hashData(file.readAll()) == hash) { + if (file.open(QIODevice::ReadOnly) && AssetUtils::hashData(file.readAll()) == hash) { qDebug() << "Not overwriting existing verified file: " << hexHash; existingCorrectFile = true; diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 725975c9fb..be58c0384c 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -34,6 +34,8 @@ MessageID AssetClient::_currentID = 0; +using AssetUtils::AssetMappingOperationType; + AssetClient::AssetClient() { _cacheDir = qApp->property(hifi::properties::APP_LOCAL_DATA_PATH).toString(); setCustomDeleter([](Dependency* dependency){ @@ -251,7 +253,7 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse ReceivedAssetCallback callback, ProgressCallback progressCallback) { Q_ASSERT(QThread::currentThread() == thread()); - if (hash.length() != SHA256_HASH_HEX_LENGTH) { + if (hash.length() != AssetUtils::SHA256_HASH_HEX_LENGTH) { qCWarning(asset_client) << "Invalid hash size"; return false; } @@ -263,7 +265,7 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse auto messageID = ++_currentID; - auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(start) + sizeof(end); + auto payloadSize = sizeof(messageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(start) + sizeof(end); auto packet = NLPacket::create(PacketType::AssetGet, payloadSize, true); qCDebug(asset_client) << "Requesting data from" << start << "to" << end << "of" << hash << "from asset-server."; @@ -295,7 +297,7 @@ MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callbac if (assetServer) { auto messageID = ++_currentID; - auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH; + auto payloadSize = sizeof(messageID) + AssetUtils::SHA256_HASH_LENGTH; auto packet = NLPacket::create(PacketType::AssetGetInfo, payloadSize, true); packet->writePrimitive(messageID); @@ -317,7 +319,7 @@ void AssetClient::handleAssetGetInfoReply(QSharedPointer messag MessageID messageID; message->readPrimitive(&messageID); - auto assetHash = message->read(SHA256_HASH_LENGTH); + auto assetHash = message->read(AssetUtils::SHA256_HASH_LENGTH); AssetServerError error; message->readPrimitive(&error); @@ -351,7 +353,7 @@ void AssetClient::handleAssetGetInfoReply(QSharedPointer messag void AssetClient::handleAssetGetReply(QSharedPointer message, SharedNodePointer senderNode) { Q_ASSERT(QThread::currentThread() == thread()); - auto assetHash = message->readHead(SHA256_HASH_LENGTH); + auto assetHash = message->readHead(AssetUtils::SHA256_HASH_LENGTH); qCDebug(asset_client) << "Got reply for asset: " << assetHash.toHex(); MessageID messageID; @@ -751,7 +753,7 @@ void AssetClient::handleAssetUploadReply(QSharedPointer message if (error) { qCWarning(asset_client) << "Error uploading file to asset server"; } else { - auto hash = message->read(SHA256_HASH_LENGTH); + auto hash = message->read(AssetUtils::SHA256_HASH_LENGTH); hashString = hash.toHex(); qCDebug(asset_client) << "Successfully uploaded asset to asset-server - SHA256 hash is " << hashString; diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 5d854390e2..6f55866740 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -42,6 +42,8 @@ struct AssetInfo { int64_t size; }; +using namespace AssetUtils; + using MappingOperationCallback = std::function message)>; using ReceivedAssetCallback = std::function; using GetInfoCallback = std::function; diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 6a2bcdca9c..a036a28cfd 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -52,7 +52,7 @@ void AssetRequest::start() { } // in case we haven't parsed a valid hash, return an error now - if (!isValidHash(_hash)) { + if (!AssetUtils::isValidHash(_hash)) { _error = InvalidHash; _state = Finished; @@ -61,7 +61,7 @@ void AssetRequest::start() { } // Try to load from cache - _data = loadFromCache(getUrl()); + _data = AssetUtils::loadFromCache(getUrl()); if (!_data.isNull()) { _error = NoError; @@ -104,7 +104,7 @@ void AssetRequest::start() { break; } } else { - if (!_byteRange.isSet() && hashData(data).toHex() != _hash) { + if (!_byteRange.isSet() && AssetUtils::hashData(data).toHex() != _hash) { // the hash of the received data does not match what we expect, so we return an error _error = HashVerificationFailed; } @@ -115,7 +115,7 @@ void AssetRequest::start() { emit progress(_totalReceived, data.size()); if (!_byteRange.isSet()) { - saveToCache(getUrl(), data); + AssetUtils::saveToCache(getUrl(), data); } } } diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h index b469e2f012..bf901c4e8f 100644 --- a/libraries/networking/src/AssetRequest.h +++ b/libraries/networking/src/AssetRequest.h @@ -52,7 +52,7 @@ public: const State& getState() const { return _state; } const Error& getError() const { return _error; } const QString getErrorString() const; - QUrl getUrl() const { return ::getATPUrl(_hash); } + QUrl getUrl() const { return AssetUtils::getATPUrl(_hash); } QString getHash() const { return _hash; } bool loadedFromCache() const { return _loadedFromCache; } diff --git a/libraries/networking/src/AssetUpload.cpp b/libraries/networking/src/AssetUpload.cpp index 077a0388cf..fabdd1febc 100644 --- a/libraries/networking/src/AssetUpload.cpp +++ b/libraries/networking/src/AssetUpload.cpp @@ -104,8 +104,8 @@ void AssetUpload::start() { } } - if (_error == NoError && hash == hashData(_data).toHex()) { - saveToCache(getATPUrl(hash), _data); + if (_error == NoError && hash == AssetUtils::hashData(_data).toHex()) { + AssetUtils::saveToCache(AssetUtils::getATPUrl(hash), _data); } emit finished(this, hash); diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index fa9885f053..45150e80c1 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -23,6 +23,8 @@ #include "ResourceManager.h" +namespace AssetUtils { + // Extract the valid AssetHash portion from atp: URLs like "[atp:]HASH[.fbx][?query]" // (or an invalid AssetHash if not found) AssetHash extractAssetHash(const QString& input) { @@ -131,3 +133,5 @@ QString bakingStatusToString(BakingStatus status) { return "--"; } } + +} // namespace AssetUtils diff --git a/libraries/networking/src/AssetUtils.h b/libraries/networking/src/AssetUtils.h index 8c89b4701c..42e8dfad62 100644 --- a/libraries/networking/src/AssetUtils.h +++ b/libraries/networking/src/AssetUtils.h @@ -19,6 +19,8 @@ #include #include +namespace AssetUtils { + using DataOffset = int64_t; using AssetPath = QString; @@ -85,18 +87,6 @@ bool isValidHash(const QString& hashString); QString bakingStatusToString(BakingStatus status); -// backwards-compatible namespace aliases -// (allows new code to be explicit -- eg: `AssetUtils::isValidPath(path)` vs. `isValidPath(path)`) -namespace AssetUtils { - static const auto& loadFromCache = ::loadFromCache; - static const auto& saveToCache = ::saveToCache; - static const auto& hashData = ::hashData; - static const auto& getATPUrl = ::getATPUrl; - static const auto& extractAssetHash = ::extractAssetHash; - static const auto& isValidFilePath = ::isValidFilePath; - static const auto& isValidPath = ::isValidPath; - static const auto& isValidHash = ::isValidHash; -}; - +} // namespace AssetUtils #endif // hifi_AssetUtils_h From 513a69cdcdb04203aad9656ce8164e827a2c0ec8 Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 3 Jan 2018 04:08:43 -0500 Subject: [PATCH 10/73] changes per CR feedback --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- libraries/entities/src/EntityTreeElement.cpp | 8 ++++---- libraries/fbx/src/FBXReader.cpp | 2 +- libraries/render-utils/src/Model.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 2814d9d06f..2ce9e35bec 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -759,7 +759,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickReleaseOn event if (!_currentClickingOnEntityID.isInvalidID()) { - glm::vec2 pos2D = projectOntoEntityXYPlane( entity, ray, rayPickResult); + glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 2daef38f39..6366c6988e 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -641,7 +641,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori // only called if we do intersect our bounding cube, but find if we actually intersect with entities... int entityNumber = 0; - EntityItemID somethingIntersected; + EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { if ( (visibleOnly && !entity->isVisible()) || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) @@ -695,7 +695,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori face = localFace; surfaceNormal = localSurfaceNormal; extraInfo = localExtraInfo; - somethingIntersected = entity->getEntityItemID(); + entityID = entity->getEntityItemID(); } } } else { @@ -705,14 +705,14 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori distance = localDistance; face = localFace; surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f)); - somethingIntersected = entity->getEntityItemID(); + entityID = entity->getEntityItemID(); } } } } entityNumber++; }); - return somethingIntersected; + return entityID; } // TODO: change this to use better bounding shape for entity than sphere diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index d7500a24c6..712211236e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1831,7 +1831,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } { - int i=0; + int i = 0; for (const auto& mesh : geometry.meshes) { auto name = geometry.getModelNameOfMesh(i++); if (!name.isEmpty()) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2aa847fdcb..ba82dfb5f0 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -403,8 +403,8 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g face = triangleSetFace; bestModelTriangle = triangleSetTriangle; bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - extraInfo["_worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); - extraInfo["_meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); bestSubMeshIndex = subMeshIndex; } } From 4021455058486c7be9c0fd7cf8d63348e0d522ec Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 3 Jan 2018 04:20:12 -0500 Subject: [PATCH 11/73] placeholder PickResult.extraInfo documentation --- interface/src/raypick/PickScriptingInterface.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0e0b3e113b..98427e34ca 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -114,6 +114,7 @@ public: * @property {float} distance The distance to the intersection point from the origin of the ray. * @property {Vec3} intersection The intersection point in world-space. * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. + * @property {Variant} extraInfo Additional intersection details when available for Model objects. * @property {PickRay} searchRay The PickRay that was used. Valid even if there was no intersection. */ @@ -127,6 +128,7 @@ public: * @property {float} distance The distance to the intersection point from the origin of the ray. * @property {Vec3} intersection The intersection point in world-space. * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. + * @property {Variant} extraInfo Additional intersection details when available for Model objects. * @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection. */ From fc41bcca5af66e995de46551e72298cb0bdf1449 Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 5 Jan 2018 18:41:51 -0500 Subject: [PATCH 12/73] removing unnecessary qWarning() debug output --- libraries/shared/src/shared/MiniPromises.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h index c5d21a22fb..0222a91d44 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -83,7 +83,6 @@ public: _onfinally << always; }); } else { - qWarning() << "MiniPromise::finally() called after promise was already rejected or resolved:" << objectName(); executeOnPromiseThread([&]{ withReadLock([&]{ always(_error, _result); @@ -105,7 +104,6 @@ public: }); } else { executeOnPromiseThread([&]{ - qWarning() << "MiniPromise::fail() called after promise was already rejected:" << objectName(); withReadLock([&]{ failFunc(_error, _result); }); @@ -126,7 +124,6 @@ public: }); } else { executeOnPromiseThread([&]{ - qWarning() << "MiniPromise::then() called after promise was already resolved:" << objectName(); withReadLock([&]{ successFunc(_error, _result); }); From 990f1db831fdf31f1aeef61d98e6a550fb88887b Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 8 Jan 2018 11:50:46 -0500 Subject: [PATCH 13/73] migrate script-archive examples .extraInfo -> .extraInfo.subMeshName + '' --- script-archive/avatarSelector.js | 4 ++-- script-archive/lobby.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/script-archive/avatarSelector.js b/script-archive/avatarSelector.js index 47740ef0b3..119044e35a 100644 --- a/script-archive/avatarSelector.js +++ b/script-archive/avatarSelector.js @@ -274,7 +274,7 @@ function actionStartEvent(event) { var result = Overlays.findRayIntersection(event.actionRay); if (result.intersects && result.overlayID == panelWall) { - var panelName = result.extraInfo; + var panelName = result.extraInfo.subMeshName + ''; var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { @@ -338,7 +338,7 @@ function handleLookAt(pickRay) { // check if we hit a panel and if we should jump there var result = Overlays.findRayIntersection(pickRay); if (result.intersects && result.overlayID == panelWall) { - var panelName = result.extraInfo; + var panelName = result.extraInfo.subMeshName + ''; var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); diff --git a/script-archive/lobby.js b/script-archive/lobby.js index 6fa4a42cb6..7a06cdd906 100644 --- a/script-archive/lobby.js +++ b/script-archive/lobby.js @@ -272,7 +272,7 @@ function actionStartEvent(event) { var result = Overlays.findRayIntersection(event.actionRay); if (result.intersects && result.overlayID == panelWall) { - var panelName = result.extraInfo; + var panelName = result.extraInfo.subMeshName + ''; var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { @@ -321,7 +321,7 @@ function handleLookAt(pickRay) { // check if we hit a panel and if we should jump there var result = Overlays.findRayIntersection(pickRay); if (result.intersects && result.overlayID == panelWall) { - var panelName = result.extraInfo; + var panelName = result.extraInfo.subMeshName + ''; var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); From ca776fb9b4db3708116e0f2e397e1ac434529aff Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 15 Jan 2018 11:36:52 +0100 Subject: [PATCH 14/73] Added LightLocal.slh to centralize shading with local lights --- libraries/render-utils/src/LightLocal.slh | 48 +++++++++++++++++++ .../render-utils/src/local_lights_shading.slf | 36 +------------- 2 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 libraries/render-utils/src/LightLocal.slh diff --git a/libraries/render-utils/src/LightLocal.slh b/libraries/render-utils/src/LightLocal.slh new file mode 100644 index 0000000000..69563d4985 --- /dev/null +++ b/libraries/render-utils/src/LightLocal.slh @@ -0,0 +1,48 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Olivier Prat on 15/01/18. +// Copyright 2018 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 +// + +// Everything about light +<@include model/Light.slh@> +<$declareLightBuffer(256)$> +<@include LightingModel.slh@> + + +<@include LightPoint.slh@> +<$declareLightingPoint(supportScattering)$> +<@include LightSpot.slh@> +<$declareLightingSpot(supportScattering)$> + +<@include LightClusterGrid.slh@> + +<@func fetchClusterInfo(fragPos)@> + + // From frag world pos find the cluster + vec4 clusterEyePos = frustumGrid_worldToEye(<$fragPos$>); + ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz); + + ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); + int numLights = cluster.x + cluster.y; + if (numLights <= 0) { + discard; + } + int lightClusterOffset = cluster.z; + + ivec3 dims = frustumGrid.dims.xyz; + if (clusterPos.x < 0 || clusterPos.x >= dims.x) { + discard; + } + + if (clusterPos.y < 0 || clusterPos.y >= dims.y) { + discard; + } + if (clusterPos.z < 0 || clusterPos.z > dims.z) { + discard; + } + +<@endfunc@> \ No newline at end of file diff --git a/libraries/render-utils/src/local_lights_shading.slf b/libraries/render-utils/src/local_lights_shading.slf index c6310cb079..3968ec9077 100644 --- a/libraries/render-utils/src/local_lights_shading.slf +++ b/libraries/render-utils/src/local_lights_shading.slf @@ -17,18 +17,7 @@ <$declareDeferredCurvature()$> -// Everything about light -<@include model/Light.slh@> -<$declareLightBuffer(256)$> -<@include LightingModel.slh@> - - -<@include LightPoint.slh@> -<$declareLightingPoint(supportScattering)$> -<@include LightSpot.slh@> -<$declareLightingSpot(supportScattering)$> - -<@include LightClusterGrid.slh@> +<@include LightLocal.slh@> in vec2 _texCoord0; out vec4 _fragColor; @@ -51,28 +40,7 @@ void main(void) { mat4 invViewMat = getViewInverse(); vec4 fragPos = invViewMat * fragPosition; - // From frag world pos find the cluster - vec4 clusterEyePos = frustumGrid_worldToEye(fragPos); - ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz); - - ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); - int numLights = cluster.x + cluster.y; - if (numLights <= 0) { - discard; - } - int lightClusterOffset = cluster.z; - - ivec3 dims = frustumGrid.dims.xyz; - if (clusterPos.x < 0 || clusterPos.x >= dims.x) { - discard; - } - - if (clusterPos.y < 0 || clusterPos.y >= dims.y) { - discard; - } - if (clusterPos.z < 0 || clusterPos.z > dims.z) { - discard; - } + <$fetchClusterInfo(fragPos)$>; vec4 midNormalCurvature; vec4 lowNormalCurvature; From 6af6b5fe41839f8155a468bd69f4e5edee02e554 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 15 Jan 2018 12:02:29 +0100 Subject: [PATCH 15/73] evalLocalLighting function created in LightLocal.slh --- libraries/render-utils/src/LightLocal.slh | 123 +++++++++++++++++- .../render-utils/src/local_lights_shading.slf | 114 +--------------- 2 files changed, 124 insertions(+), 113 deletions(-) diff --git a/libraries/render-utils/src/LightLocal.slh b/libraries/render-utils/src/LightLocal.slh index 69563d4985..dac5f74bc8 100644 --- a/libraries/render-utils/src/LightLocal.slh +++ b/libraries/render-utils/src/LightLocal.slh @@ -20,10 +20,10 @@ <@include LightClusterGrid.slh@> -<@func fetchClusterInfo(fragPos)@> +<@func fetchClusterInfo(fragWorldPos)@> // From frag world pos find the cluster - vec4 clusterEyePos = frustumGrid_worldToEye(<$fragPos$>); + vec4 clusterEyePos = frustumGrid_worldToEye(<$fragWorldPos$>); ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz); ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); @@ -31,7 +31,6 @@ if (numLights <= 0) { discard; } - int lightClusterOffset = cluster.z; ivec3 dims = frustumGrid.dims.xyz; if (clusterPos.x < 0 || clusterPos.x >= dims.x) { @@ -45,4 +44,120 @@ discard; } -<@endfunc@> \ No newline at end of file +<@endfunc@> + +vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, vec3 fragNormal, vec3 fragEyeDir, + vec4 midNormalCurvature, vec4 lowNormalCurvature, + float fragRoughness, float fragScattering, float fragMetallic, vec3 fragFresnel, vec3 fragAlbedo) { + vec4 _fragColor = vec4(0.0); + int lightClusterOffset = cluster.z; + + // Compute the rougness into gloss2 once: + float fragGloss2 = pow(fragRoughness + 0.001, 4.0); + bool withScattering = (fragScattering * isScatteringEnabled() > 0.0); + + int numLightTouching = 0; + for (int i = 0; i < cluster.x; i++) { + // Need the light now + int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset); + Light light = getLight(theLightIndex); + + // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space + vec4 fragLightVecLen2; + vec4 fragLightDirLen; + + if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragWorldPos.xyz, fragLightVecLen2)) { + continue; + } + + // Allright we re in the light sphere volume + fragLightDirLen.w = length(fragLightVecLen2.xyz); + fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w; + if (dot(fragNormal, fragLightDirLen.xyz) < 0.0) { + continue; + } + + numLightTouching++; + + vec3 diffuse = vec3(1.0); + vec3 specular = vec3(0.1); + + // Allright we re valid in the volume + float fragLightDistance = fragLightDirLen.w; + vec3 fragLightDir = fragLightDirLen.xyz; + + // Eval attenuation + float radialAttenuation = lightIrradiance_evalLightAttenuation(light.irradiance, fragLightDistance); + vec3 lightEnergy = radialAttenuation * getLightIrradiance(light); + + // Eval shading + if (withScattering) { + evalFragShadingScattering(diffuse, specular, fragNormal, fragLightDir, fragEyeDir, fragMetallic, fragFresnel, fragRoughness, fragAlbedo + ,fragScattering, midNormalCurvature, lowNormalCurvature ); + } else { + evalFragShadingGloss(diffuse, specular, fragNormal, fragLightDir, fragEyeDir, fragMetallic, fragFresnel, fragGloss2, fragAlbedo); + } + + diffuse *= lightEnergy * isDiffuseEnabled(); + specular *= lightEnergy * isSpecularEnabled(); + + _fragColor.rgb += diffuse; + _fragColor.rgb += specular; + } + + for (int i = cluster.x; i < numLights; i++) { + // Need the light now + int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset); + Light light = getLight(theLightIndex); + + // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space + vec4 fragLightVecLen2; + vec4 fragLightDirLen; + float cosSpotAngle; + + if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragWorldPos.xyz, fragLightVecLen2)) { + continue; + } + + // Allright we re in the light sphere volume + fragLightDirLen.w = length(fragLightVecLen2.xyz); + fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w; + if (dot(fragNormal, fragLightDirLen.xyz) < 0.0) { + continue; + } + + // Check spot + if (!lightVolume_clipFragToLightVolumeSpotSide(light.volume, fragLightDirLen, cosSpotAngle)) { + continue; + } + + numLightTouching++; + + vec3 diffuse = vec3(1.0); + vec3 specular = vec3(0.1); + + // Allright we re valid in the volume + float fragLightDistance = fragLightDirLen.w; + vec3 fragLightDir = fragLightDirLen.xyz; + + // Eval attenuation + float radialAttenuation = lightIrradiance_evalLightAttenuation(light.irradiance, fragLightDistance); + float angularAttenuation = lightIrradiance_evalLightSpotAttenuation(light.irradiance, cosSpotAngle); + vec3 lightEnergy = radialAttenuation * angularAttenuation * getLightIrradiance(light); + + // Eval shading + if (withScattering) { + evalFragShadingScattering(diffuse, specular, fragNormal, fragLightDir, fragEyeDir, fragMetallic, fragFresnel, fragRoughness, fragAlbedo + ,fragScattering, midNormalCurvature, lowNormalCurvature ); + } else { + evalFragShadingGloss(diffuse, specular, fragNormal, fragLightDir, fragEyeDir, fragMetallic, fragFresnel, fragGloss2, fragAlbedo); + } + + diffuse *= lightEnergy * isDiffuseEnabled(); + specular *= lightEnergy * isSpecularEnabled(); + + _fragColor.rgb += diffuse; + _fragColor.rgb += specular; + } + return _fragColor; +} \ No newline at end of file diff --git a/libraries/render-utils/src/local_lights_shading.slf b/libraries/render-utils/src/local_lights_shading.slf index 3968ec9077..f302c6daba 100644 --- a/libraries/render-utils/src/local_lights_shading.slf +++ b/libraries/render-utils/src/local_lights_shading.slf @@ -38,9 +38,9 @@ void main(void) { // Frag pos in world mat4 invViewMat = getViewInverse(); - vec4 fragPos = invViewMat * fragPosition; + vec4 fragWorldPos = invViewMat * fragPosition; - <$fetchClusterInfo(fragPos)$>; + <$fetchClusterInfo(fragWorldPos)$>; vec4 midNormalCurvature; vec4 lowNormalCurvature; @@ -53,113 +53,9 @@ void main(void) { vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - // Compute the rougness into gloss2 once: - float fragGloss2 = pow(frag.roughness + 0.001, 4.0); - bool withScattering = (frag.scattering * isScatteringEnabled() > 0.0); - - int numLightTouching = 0; - for (int i = 0; i < cluster.x; i++) { - // Need the light now - int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset); - Light light = getLight(theLightIndex); - - // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space - vec4 fragLightVecLen2; - vec4 fragLightDirLen; - - if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragPos.xyz, fragLightVecLen2)) { - continue; - } - - // Allright we re in the light sphere volume - fragLightDirLen.w = length(fragLightVecLen2.xyz); - fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w; - if (dot(frag.normal, fragLightDirLen.xyz) < 0.0) { - continue; - } - - numLightTouching++; - - vec3 diffuse = vec3(1.0); - vec3 specular = vec3(0.1); - - // Allright we re valid in the volume - float fragLightDistance = fragLightDirLen.w; - vec3 fragLightDir = fragLightDirLen.xyz; - - // Eval attenuation - float radialAttenuation = lightIrradiance_evalLightAttenuation(light.irradiance, fragLightDistance); - vec3 lightEnergy = radialAttenuation * getLightIrradiance(light); - - // Eval shading - if (withScattering) { - evalFragShadingScattering(diffuse, specular, frag.normal, fragLightDir, fragEyeDir, frag.metallic, frag.fresnel, frag.roughness, frag.albedo - ,frag.scattering, midNormalCurvature, lowNormalCurvature ); - } else { - evalFragShadingGloss(diffuse, specular, frag.normal, fragLightDir, fragEyeDir, frag.metallic, frag.fresnel, fragGloss2, frag.albedo); - } - - diffuse *= lightEnergy * isDiffuseEnabled(); - specular *= lightEnergy * isSpecularEnabled(); - - _fragColor.rgb += diffuse; - _fragColor.rgb += specular; - } - - for (int i = cluster.x; i < numLights; i++) { - // Need the light now - int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset); - Light light = getLight(theLightIndex); - - // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space - vec4 fragLightVecLen2; - vec4 fragLightDirLen; - float cosSpotAngle; - - if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragPos.xyz, fragLightVecLen2)) { - continue; - } - - // Allright we re in the light sphere volume - fragLightDirLen.w = length(fragLightVecLen2.xyz); - fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w; - if (dot(frag.normal, fragLightDirLen.xyz) < 0.0) { - continue; - } - - // Check spot - if (!lightVolume_clipFragToLightVolumeSpotSide(light.volume, fragLightDirLen, cosSpotAngle)) { - continue; - } - - numLightTouching++; - - vec3 diffuse = vec3(1.0); - vec3 specular = vec3(0.1); - - // Allright we re valid in the volume - float fragLightDistance = fragLightDirLen.w; - vec3 fragLightDir = fragLightDirLen.xyz; - - // Eval attenuation - float radialAttenuation = lightIrradiance_evalLightAttenuation(light.irradiance, fragLightDistance); - float angularAttenuation = lightIrradiance_evalLightSpotAttenuation(light.irradiance, cosSpotAngle); - vec3 lightEnergy = radialAttenuation * angularAttenuation * getLightIrradiance(light); - - // Eval shading - if (withScattering) { - evalFragShadingScattering(diffuse, specular, frag.normal, fragLightDir, fragEyeDir, frag.metallic, frag.fresnel, frag.roughness, frag.albedo - ,frag.scattering, midNormalCurvature, lowNormalCurvature ); - } else { - evalFragShadingGloss(diffuse, specular, frag.normal, fragLightDir, fragEyeDir, frag.metallic, frag.fresnel, fragGloss2, frag.albedo); - } - - diffuse *= lightEnergy * isDiffuseEnabled(); - specular *= lightEnergy * isSpecularEnabled(); - - _fragColor.rgb += diffuse; - _fragColor.rgb += specular; - } + _fragColor = evalLocalLighting(cluster, numLights, fragWorldPos.xyz, frag.normal, fragEyeDir, + midNormalCurvature, lowNormalCurvature, frag.roughness, frag.scattering, + frag.metallic, frag.fresnel, frag.albedo); } From a3e50689cbf27d45a705385879e4012542abc715 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 15 Jan 2018 12:18:22 +0100 Subject: [PATCH 16/73] Modified light outline shader to use some factorized code --- .../render-utils/src/LightClusterGrid.slh | 27 +++++++++++++++++++ libraries/render-utils/src/LightLocal.slh | 26 ------------------ .../src/local_lights_drawOutline.slf | 24 ++--------------- 3 files changed, 29 insertions(+), 48 deletions(-) diff --git a/libraries/render-utils/src/LightClusterGrid.slh b/libraries/render-utils/src/LightClusterGrid.slh index 722d37814d..d2eb213c71 100644 --- a/libraries/render-utils/src/LightClusterGrid.slh +++ b/libraries/render-utils/src/LightClusterGrid.slh @@ -86,4 +86,31 @@ int clusterGrid_getClusterLightId(int index, int offset) { return (((elementIndex & 0x00000001) == 1) ? (element >> 16) : element) & 0x0000FFFF; } + +<@func fetchClusterInfo(fragWorldPos)@> + + // From frag world pos find the cluster + vec4 clusterEyePos = frustumGrid_worldToEye(<$fragWorldPos$>); + ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz); + + ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); + int numLights = cluster.x + cluster.y; + if (numLights <= 0) { + discard; + } + + ivec3 dims = frustumGrid.dims.xyz; + if (clusterPos.x < 0 || clusterPos.x >= dims.x) { + discard; + } + + if (clusterPos.y < 0 || clusterPos.y >= dims.y) { + discard; + } + if (clusterPos.z < 0 || clusterPos.z > dims.z) { + discard; + } + +<@endfunc@> + <@endif@> diff --git a/libraries/render-utils/src/LightLocal.slh b/libraries/render-utils/src/LightLocal.slh index dac5f74bc8..d9980b1fa6 100644 --- a/libraries/render-utils/src/LightLocal.slh +++ b/libraries/render-utils/src/LightLocal.slh @@ -20,32 +20,6 @@ <@include LightClusterGrid.slh@> -<@func fetchClusterInfo(fragWorldPos)@> - - // From frag world pos find the cluster - vec4 clusterEyePos = frustumGrid_worldToEye(<$fragWorldPos$>); - ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz); - - ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); - int numLights = cluster.x + cluster.y; - if (numLights <= 0) { - discard; - } - - ivec3 dims = frustumGrid.dims.xyz; - if (clusterPos.x < 0 || clusterPos.x >= dims.x) { - discard; - } - - if (clusterPos.y < 0 || clusterPos.y >= dims.y) { - discard; - } - if (clusterPos.z < 0 || clusterPos.z > dims.z) { - discard; - } - -<@endfunc@> - vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, vec3 fragNormal, vec3 fragEyeDir, vec4 midNormalCurvature, vec4 lowNormalCurvature, float fragRoughness, float fragScattering, float fragMetallic, vec3 fragFresnel, vec3 fragAlbedo) { diff --git a/libraries/render-utils/src/local_lights_drawOutline.slf b/libraries/render-utils/src/local_lights_drawOutline.slf index 3aa210a241..f7527d0542 100644 --- a/libraries/render-utils/src/local_lights_drawOutline.slf +++ b/libraries/render-utils/src/local_lights_drawOutline.slf @@ -54,34 +54,14 @@ void main(void) { mat4 invViewMat = getViewInverse(); vec4 fragPos = invViewMat * fragPosition; - // From frag world pos find the cluster - vec4 clusterEyePos = frustumGrid_worldToEye(fragPos); - ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz); - - ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); - int numLights = cluster.x + cluster.y; - if (numLights <= 0) { - discard; - } - int lightClusterOffset = cluster.z; - - ivec3 dims = frustumGrid.dims.xyz; - if (clusterPos.x < 0 || clusterPos.x >= dims.x) { - discard; - } - - if (clusterPos.y < 0 || clusterPos.y >= dims.y) { - discard; - } - if (clusterPos.z < 0 || clusterPos.z > dims.z) { - discard; - } + <$fetchClusterInfo(fragPos)$>; // Frag to eye vec vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); int numLightTouching = 0; + int lightClusterOffset = cluster.z; for (int i = 0; i < cluster.x; i++) { // Need the light now int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset); From 54eab1c878d1e5ccb534d1d605b9808254c9b6c2 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 15 Jan 2018 17:49:55 +0100 Subject: [PATCH 17/73] First working local lights on transparent objects --- libraries/model/src/model/Light.slh | 4 +- .../render-utils/src/DeferredGlobalLight.slh | 4 +- .../src/DeferredLightingEffect.cpp | 37 ++++++++++++---- .../render-utils/src/DeferredLightingEffect.h | 3 ++ libraries/render-utils/src/DrawHaze.cpp | 2 +- .../render-utils/src/ForwardGlobalLight.slh | 4 +- libraries/render-utils/src/Haze.slf | 2 +- .../render-utils/src/RenderDeferredTask.cpp | 19 +++++++- .../render-utils/src/RenderDeferredTask.h | 3 +- .../render-utils/src/RenderPipelines.cpp | 30 ++++++++----- .../render-utils/src/SubsurfaceScattering.cpp | 2 +- libraries/render-utils/src/ZoneRenderer.cpp | 2 +- .../render-utils/src/model_translucent.slf | 13 ++++++ .../render-utils/src/model_translucent.slv | 44 +++++++++++++++++++ .../src/model_translucent_fade.slf | 12 +++++ libraries/render-utils/src/overlay3D.slf | 2 +- .../src/overlay3D_translucent.slf | 2 +- .../subsurfaceScattering_drawScattering.slf | 4 +- .../render-utils/src/zone_drawKeyLight.slf | 2 +- libraries/render/src/render/ShapePipeline.cpp | 20 ++++++++- libraries/render/src/render/ShapePipeline.h | 9 ++++ 21 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 libraries/render-utils/src/model_translucent.slv diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 093a87adc8..a7960e473e 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -56,10 +56,10 @@ Light getLight(int index) { } <@else@> -uniform lightBuffer { +uniform keyLightBuffer { Light light; }; -Light getLight() { +Light getKeyLight() { return light; } diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index de2d41be6b..8406e4a0c5 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -29,7 +29,7 @@ vec3 fragEyeDir = normalize(fragEyeVector); // Get light - Light light = getLight(); + Light light = getKeyLight(); LightAmbient lightAmbient = getLightAmbient(); vec3 lightDirection = getLightDirection(light); @@ -143,7 +143,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu <@func declareEvalLightmappedColor()@> vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 albedo, vec3 lightmap) { - Light light = getLight(); + Light light = getKeyLight(); LightAmbient ambient = getLightAmbient(); // Catch normals perpendicular to the projection plane, hence the magic number for the threshold diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 81a33f17e3..227c1ce339 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -42,6 +42,7 @@ using namespace render; struct LightLocations { int radius{ -1 }; + int keyLightBufferUnit{ -1 }; int lightBufferUnit{ -1 }; int ambientBufferUnit { -1 }; int lightIndexBufferUnit { -1 }; @@ -144,6 +145,29 @@ void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBuff } } +void DeferredLightingEffect::setupLocalLightsBatch(gpu::Batch& batch, + int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit, + const LightClustersPointer& lightClusters) { + // Bind the global list of lights and the visible lights this frame + batch.setUniformBuffer(_localLightLocations->lightBufferUnit, lightClusters->_lightStage->getLightArrayBuffer()); + + batch.setUniformBuffer(frustumGridBufferUnit, lightClusters->_frustumGridBuffer); + batch.setUniformBuffer(clusterGridBufferUnit, lightClusters->_clusterGridBuffer); + batch.setUniformBuffer(clusterContentBufferUnit, lightClusters->_clusterContentBuffer); +} + +void DeferredLightingEffect::unsetLocalLightsBatch(gpu::Batch& batch, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit) { + if (clusterGridBufferUnit >= 0) { + batch.setUniformBuffer(clusterGridBufferUnit, nullptr); + } + if (clusterContentBufferUnit >= 0) { + batch.setUniformBuffer(clusterContentBufferUnit, nullptr); + } + if (frustumGridBufferUnit >= 0) { + batch.setUniformBuffer(frustumGridBufferUnit, nullptr); + } +} + static gpu::ShaderPointer makeLightProgram(const char* vertSource, const char* fragSource, LightLocationsPtr& locations) { auto VS = gpu::Shader::createVertex(std::string(vertSource)); auto PS = gpu::Shader::createPixel(std::string(fragSource)); @@ -186,6 +210,7 @@ static gpu::ShaderPointer makeLightProgram(const char* vertSource, const char* f locations->texcoordFrameTransform = program->getUniforms().findLocation("texcoordFrameTransform"); + locations->keyLightBufferUnit = program->getUniformBuffers().findLocation("keyLightBuffer"); locations->lightBufferUnit = program->getUniformBuffers().findLocation("lightBuffer"); locations->ambientBufferUnit = program->getUniformBuffers().findLocation("lightAmbientBuffer"); locations->lightIndexBufferUnit = program->getUniformBuffers().findLocation("lightIndexBuffer"); @@ -558,7 +583,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch._glUniform4fv(locations->texcoordFrameTransform, 1, reinterpret_cast< const float* >(&textureFrameTransform)); // Setup the global lighting - deferredLightingEffect->setupKeyLightBatch(args, batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT); + deferredLightingEffect->setupKeyLightBatch(args, batch, locations->keyLightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT); // Haze if (haze) { @@ -567,7 +592,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.draw(gpu::TRIANGLE_STRIP, 4); - deferredLightingEffect->unsetKeyLightBatch(batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT); + deferredLightingEffect->unsetKeyLightBatch(batch, locations->keyLightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { batch.setResourceTexture(SHADOW_MAP_UNIT+i, nullptr); @@ -622,12 +647,8 @@ void RenderDeferredLocals::run(const render::RenderContextPointer& renderContext auto& lightIndices = lightClusters->_visibleLightIndices; if (!lightIndices.empty() && lightIndices[0] > 0) { - // Bind the global list of lights and the visible lights this frame - batch.setUniformBuffer(deferredLightingEffect->_localLightLocations->lightBufferUnit, lightClusters->_lightStage->getLightArrayBuffer()); - - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustumGridBuffer); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, lightClusters->_clusterGridBuffer); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, lightClusters->_clusterContentBuffer); + deferredLightingEffect->setupLocalLightsBatch(batch, LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, + lightClusters); // Local light pipeline batch.setPipeline(deferredLightingEffect->_localLight); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 212d17db12..1607cb2c85 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -51,6 +51,9 @@ public: void setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); void unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); + void setupLocalLightsBatch(gpu::Batch& batch, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit, const LightClustersPointer& lightClusters); + void unsetLocalLightsBatch(gpu::Batch& batch, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit); + void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; }; void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; } bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; } diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index da07f5bd9b..783dadba7c 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -147,7 +147,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), HazeEffect_TransformBufferSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), HazeEffect_ColorMapSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), HazeEffect_LinearDepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), HazeEffect_LightingMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), HazeEffect_LightingMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); _hazePipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); diff --git a/libraries/render-utils/src/ForwardGlobalLight.slh b/libraries/render-utils/src/ForwardGlobalLight.slh index aba0498ef5..cde37e0ba7 100644 --- a/libraries/render-utils/src/ForwardGlobalLight.slh +++ b/libraries/render-utils/src/ForwardGlobalLight.slh @@ -29,7 +29,7 @@ vec3 fragEyeDir = normalize(fragEyeVector); // Get light - Light light = getLight(); + Light light = getKeyLight(); LightAmbient lightAmbient = getLightAmbient(); vec3 lightDirection = getLightDirection(light); @@ -139,7 +139,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu <@func declareEvalLightmappedColor()@> vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 albedo, vec3 lightmap) { - Light light = getLight(); + Light light = getKeyLight(); LightAmbient ambient = getLightAmbient(); // Catch normals perpendicular to the projection plane, hence the magic number for the threshold diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index 0270aa58f0..b3b1e6d979 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -53,7 +53,7 @@ void main(void) { vec4 worldFragPos = viewInverse * eyeFragPos; vec4 worldEyePos = viewInverse[3]; - Light light = getLight(); + Light light = getKeyLight(); vec3 lightDirection = getLightDirection(light); outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.y, lightDirection); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index f860c0494e..3aa8538fbc 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -12,6 +12,8 @@ #include "RenderDeferredTask.h" +#include + #include #include #include @@ -167,7 +169,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawHazeDeferred", drawHazeInputs); // Render transparent objects forward in LightingBuffer - const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying(); + const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel, lightClusters).asVarying(); task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); // Light Cluster Grid Debuging job @@ -314,6 +316,8 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); + const auto& lightClusters = inputs.get2(); + auto deferredLightingEffect = DependencyManager::get(); RenderArgs* args = renderContext->args; @@ -335,7 +339,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); - // Setup haze iff curretn zone has haze + deferredLightingEffect->setupLocalLightsBatch(batch, + render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, + render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, + render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, + lightClusters); + + // Setup haze if current zone has haze auto hazeStage = args->_scene->getStage(); if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); @@ -355,6 +365,11 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& args->_batch = nullptr; args->_globalShapeKey = 0; + + deferredLightingEffect->unsetLocalLightsBatch(batch, + render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, + render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, + render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT); }); config->setNumDrawn((int)inItems.size()); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index e41b7edc77..7e439a547f 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -15,6 +15,7 @@ #include #include #include "LightingModel.h" +#include "LightClusters.h" class BeginGPURangeTimer { public: @@ -65,7 +66,7 @@ protected: class DrawDeferred { public: - using Inputs = render::VaryingSet2 ; + using Inputs = render::VaryingSet3 ; using Config = DrawConfig; using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 7f644add72..668d524018 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -29,6 +29,7 @@ #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" +#include "model_translucent_vert.h" #include "skin_model_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" @@ -188,6 +189,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelNormalMapVertex = gpu::Shader::createVertex(std::string(model_normal_map_vert)); auto modelLightmapVertex = gpu::Shader::createVertex(std::string(model_lightmap_vert)); auto modelLightmapNormalMapVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_vert)); + auto modelTranslucentVertex = gpu::Shader::createVertex(std::string(model_translucent_vert)); auto modelShadowVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); auto skinModelVertex = gpu::Shader::createVertex(std::string(skin_model_vert)); auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert)); @@ -196,9 +198,13 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelLightmapNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_fade_vert)); auto skinModelFadeVertex = gpu::Shader::createVertex(std::string(skin_model_fade_vert)); auto skinModelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_fade_vert)); + auto skinModelTranslucentVertex = skinModelFadeVertex; // We use the same because it ouputs world position per vertex + auto skinModelNormalMapTranslucentVertex = skinModelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_fade_vert)); + auto modelTranslucentFadeVertex = modelTranslucentVertex; // We use the same because it ouputs world position per vertex auto modelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_normal_map_fade_vert)); + auto modelNormalMapTranslucentVertex = modelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex auto simpleFadeVertex = gpu::Shader::createVertex(std::string(simple_fade_vert)); auto modelShadowFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); auto skinModelShadowFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); @@ -289,7 +295,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip // Translucents addPipeline( Key::Builder().withMaterial().withTranslucent(), - modelVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withTranslucent(), simpleVertex, simpleTranslucentPixel, nullptr, nullptr); @@ -301,21 +307,21 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); + modelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withSpecular(), - modelVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withSpecular(), - modelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); + modelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), - modelVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withTranslucent().withFade(), - modelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + modelTranslucentFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withTranslucent().withFade(), simpleFadeVertex, simpleTranslucentFadePixel, batchSetter, itemSetter); @@ -396,16 +402,16 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip // Skinned and Translucent addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent(), - skinModelVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), - skinModelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withSpecular(), - skinModelVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular(), - skinModelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(), @@ -565,9 +571,9 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderAr batchSetter(pipeline, batch, args); // Set the light - if (pipeline.locations->lightBufferUnit >= 0) { + if (pipeline.locations->keyLightBufferUnit >= 0) { DependencyManager::get()->setupKeyLightBatch(args, batch, - pipeline.locations->lightBufferUnit, + pipeline.locations->keyLightBufferUnit, pipeline.locations->lightAmbientBufferUnit, pipeline.locations->lightAmbientMapUnit); } diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 1786898e57..2183f95565 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -463,7 +463,7 @@ gpu::PipelinePointer DebugSubsurfaceScattering::getScatteringPipeline() { gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ScatteringTask_FrameTransformSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("scatteringParamsBuffer"), ScatteringTask_ParamSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ScatteringTask_LightSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), ScatteringTask_LightSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("scatteringLUT"), ScatteringTask_ScatteringTableSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("curvatureMap"), ScatteringTask_CurvatureMapSlot)); diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index c0d01c2eaf..ce2957dc9f 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -83,7 +83,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ZONE_KEYLIGHT_BUFFER)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), ZONE_KEYLIGHT_BUFFER)); gpu::Shader::makeProgram(*program, slotBindings); diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 38f162fdc3..5f61aea889 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -18,6 +18,8 @@ <$declareEvalGlobalLightingAlphaBlended()$> +<@include LightLocal.slh@> + <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> @@ -27,6 +29,7 @@ in vec2 _texCoord0; in vec2 _texCoord1; in vec4 _position; +in vec4 _worldPosition; in vec3 _normal; in vec3 _color; in float _alpha; @@ -66,6 +69,16 @@ void main(void) { TransformCamera cam = getTransformCamera(); + <$fetchClusterInfo(_worldPosition)$>; + + vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); + vec3 fragEyeDir = normalize(fragEyeVector); + + vec4 localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, fragNormal, fragEyeDir, + vec4(0), vec4(0), roughness, 0.0, + metallic, fresnel, albedo); + emissive += localLighting.rgb; + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, diff --git a/libraries/render-utils/src/model_translucent.slv b/libraries/render-utils/src/model_translucent.slv new file mode 100644 index 0000000000..2fe4c511f0 --- /dev/null +++ b/libraries/render-utils/src/model_translucent.slv @@ -0,0 +1,44 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// model_translucent.slv +// vertex shader +// +// Created by Olivier Prat on 15/01/18. +// Copyright 2018 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 gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out float _alpha; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec4 _position; +out vec4 _worldPosition; +out vec3 _normal; +out vec3 _color; + +void main(void) { + _color = colorToLinearRGB(inColor.xyz); + _alpha = inColor.w; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> +} diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index 9d5477304c..e07730a07e 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -18,6 +18,8 @@ <$declareEvalGlobalLightingAlphaBlended()$> +<@include LightLocal.slh@> + <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> @@ -76,6 +78,16 @@ void main(void) { TransformCamera cam = getTransformCamera(); + <$fetchClusterInfo(_worldPosition)$>; + + vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); + vec3 fragEyeDir = normalize(fragEyeVector); + + vec4 localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, fragNormal, fragEyeDir, + vec4(0), vec4(0), roughness, 0.0, + metallic, fresnel, albedo); + emissive += localLighting.rgb; + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, diff --git a/libraries/render-utils/src/overlay3D.slf b/libraries/render-utils/src/overlay3D.slf index 0cb3340845..683e1420bc 100644 --- a/libraries/render-utils/src/overlay3D.slf +++ b/libraries/render-utils/src/overlay3D.slf @@ -27,7 +27,7 @@ vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) { // Need the light now - Light light = getLight(); + Light light = getKeyLight(); vec3 lightDirection = getLightDirection(light); vec3 lightIrradiance = getLightIrradiance(light); diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf index 9bdac2d21f..c39d4b0c3b 100644 --- a/libraries/render-utils/src/overlay3D_translucent.slf +++ b/libraries/render-utils/src/overlay3D_translucent.slf @@ -27,7 +27,7 @@ vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) { // Need the light now - Light light = getLight(); + Light light = getKeyLight(); vec3 lightDirection = getLightDirection(light); vec3 lightIrradiance = getLightIrradiance(light); diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index 981993615c..6f52e02040 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -42,7 +42,7 @@ vec3 evalScatteringBRDF(vec2 texcoord) { vec3 fragNormal = vec3((normal)); // Get light - Light light = getLight(); + Light light = getKeyLight(); vec3 fresnel = vec3(0.028); // Default Di-electric fresnel value for skin float metallic = 0.0; @@ -65,7 +65,7 @@ vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { float curvature = unpackCurvature(diffusedCurvature.w); // Get light - Light light = getLight(); + Light light = getKeyLight(); vec3 fresnel = vec3(0.028); // Default Di-electric fresnel value for skin vec3 fragLightDir = -normalize(getLightDirection(light)); diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index e96239b6dc..f7caf9b44a 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -25,7 +25,7 @@ void main(void) { <$evalGlobeWidget()$> - Light light = getLight(); + Light light = getKeyLight(); vec3 lightDirection = normalize(getLightDirection(light)); vec3 lightIrradiance = getLightIrradiance(light); vec3 color = vec3(0.0); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 4254280fa1..27fe6604ff 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -70,6 +70,8 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state, BatchSetter batchSetter, ItemSetter itemSetter) { + ShapeKey key{ filter._flags }; + gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL)); slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING)); @@ -82,6 +84,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP)); slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION)); slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), Slot::BUFFER::KEY_LIGHT)); slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT)); slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER)); slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); @@ -89,6 +92,12 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); slotBindings.insert(gpu::Shader::Binding(std::string("hazeParametersBuffer"), Slot::BUFFER::HAZE_MODEL)); + if (key.isTranslucent()) { + slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); + } + gpu::Shader::makeProgram(*program, slotBindings); auto locations = std::make_shared(); @@ -103,14 +112,23 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->skinClusterBufferUnit = program->getUniformBuffers().findLocation("skinClusterBuffer"); locations->materialBufferUnit = program->getUniformBuffers().findLocation("materialBuffer"); locations->texMapArrayBufferUnit = program->getUniformBuffers().findLocation("texMapArrayBuffer"); + locations->keyLightBufferUnit = program->getUniformBuffers().findLocation("keyLightBuffer"); locations->lightBufferUnit = program->getUniformBuffers().findLocation("lightBuffer"); locations->lightAmbientBufferUnit = program->getUniformBuffers().findLocation("lightAmbientBuffer"); locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeParametersBuffer"); + if (key.isTranslucent()) { + locations->lightClusterGridBufferUnit = program->getUniformBuffers().findLocation("clusterGridBuffer"); + locations->lightClusterContentBufferUnit = program->getUniformBuffers().findLocation("clusterContentBuffer"); + locations->lightClusterFrustumBufferUnit = program->getUniformBuffers().findLocation("frustumGridBuffer"); + } else { + locations->lightClusterGridBufferUnit = -1; + locations->lightClusterContentBufferUnit = -1; + locations->lightClusterFrustumBufferUnit = -1; + } - ShapeKey key{filter._flags}; auto gpuPipeline = gpu::Pipeline::create(program, state); auto shapePipeline = std::make_shared(gpuPipeline, locations, batchSetter, itemSetter); addPipelineHelper(filter, key, 0, shapePipeline); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index be77e2f95e..c84f7efba5 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -235,10 +235,15 @@ public: MATERIAL, TEXMAPARRAY, LIGHTING_MODEL, + KEY_LIGHT, LIGHT, LIGHT_AMBIENT_BUFFER, HAZE_MODEL, FADE_PARAMETERS, + LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, + LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, + LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, + }; enum MAP { @@ -266,12 +271,16 @@ public: int skinClusterBufferUnit; int materialBufferUnit; int texMapArrayBufferUnit; + int keyLightBufferUnit; int lightBufferUnit; int lightAmbientBufferUnit; int lightAmbientMapUnit; int fadeMaskTextureUnit; int fadeParameterBufferUnit; int hazeParameterBufferUnit; + int lightClusterGridBufferUnit; + int lightClusterContentBufferUnit; + int lightClusterFrustumBufferUnit; }; using LocationsPointer = std::shared_ptr; From 32445a566017b9c59cf499e6cda2e67b25a08bb8 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 15 Jan 2018 18:18:13 +0100 Subject: [PATCH 18/73] Updated pipelines --- .../render-utils/src/RenderPipelines.cpp | 8 ++-- .../src/model_translucent_fade.slv | 44 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 libraries/render-utils/src/model_translucent_fade.slv diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 668d524018..e9b1b59016 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -30,6 +30,7 @@ #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" #include "model_translucent_vert.h" +#include "model_translucent_fade_vert.h" #include "skin_model_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" @@ -190,6 +191,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelLightmapVertex = gpu::Shader::createVertex(std::string(model_lightmap_vert)); auto modelLightmapNormalMapVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_vert)); auto modelTranslucentVertex = gpu::Shader::createVertex(std::string(model_translucent_vert)); + auto modelTranslucentFadeVertex = gpu::Shader::createVertex(std::string(model_translucent_fade_vert)); auto modelShadowVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); auto skinModelVertex = gpu::Shader::createVertex(std::string(skin_model_vert)); auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert)); @@ -202,9 +204,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto skinModelNormalMapTranslucentVertex = skinModelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_fade_vert)); - auto modelTranslucentFadeVertex = modelTranslucentVertex; // We use the same because it ouputs world position per vertex auto modelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_normal_map_fade_vert)); - auto modelNormalMapTranslucentVertex = modelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex auto simpleFadeVertex = gpu::Shader::createVertex(std::string(simple_fade_vert)); auto modelShadowFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); auto skinModelShadowFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); @@ -307,13 +307,13 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withSpecular(), modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withSpecular(), - modelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), diff --git a/libraries/render-utils/src/model_translucent_fade.slv b/libraries/render-utils/src/model_translucent_fade.slv new file mode 100644 index 0000000000..eaabdd27e8 --- /dev/null +++ b/libraries/render-utils/src/model_translucent_fade.slv @@ -0,0 +1,44 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// model_translucent_fade.slv +// vertex shader +// +// Created by Olivier Prat on 15/01/18. +// Copyright 2018 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 gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out float _alpha; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec4 _position; +out vec4 _worldPosition; +out vec3 _normal; +out vec3 _color; + +void main(void) { + _color = colorToLinearRGB(inColor.xyz); + _alpha = inColor.w; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> +} From 69389116f5f4b21b93f7b3935f7e33847566785f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 15 Jan 2018 16:47:48 -0800 Subject: [PATCH 19/73] add menu option for AvatarEntitiesBookmarks --- interface/src/Application.cpp | 4 ++ interface/src/AvatarEntitiesBookmarks.cpp | 85 +++++++++++++++++++++++ interface/src/AvatarEntitiesBookmarks.h | 45 ++++++++++++ interface/src/Menu.cpp | 4 ++ interface/src/Menu.h | 2 + 5 files changed, 140 insertions(+) create mode 100644 interface/src/AvatarEntitiesBookmarks.cpp create mode 100644 interface/src/AvatarEntitiesBookmarks.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 449b014c13..a23ff2bf8d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -701,6 +702,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -2367,6 +2369,7 @@ void Application::initializeUi() { surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); + surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get().data()); surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get().data()); // Caches @@ -5762,6 +5765,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get()->getStats().data()); scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get().data()); scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("RayPick", DependencyManager::get().data()); diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp new file mode 100644 index 0000000000..e034aec458 --- /dev/null +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -0,0 +1,85 @@ +// +// AvatarEntitiesBookmarks.cpp +// interface/src +// +// Created by Dante Ruiz on /01/18. +// 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 +// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "MainWindow.h" +#include "Menu.h" +#include "AvatarEntitiesBookmarks.h" +#include "InterfaceLogging.h" + +#include "QVariantGLM.h" + +#include + +AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() { + _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME; + readFromFile(); +} + +void AvatarEntitiesBookmarks::readFromFile() { + QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME; + QFile oldConfig(oldConfigPath); + // I imagine that in a year from now, this code for migrating (as well as the two lines above) + // may be removed since all bookmarks should have been migrated by then + // - Robbie Uvanni (6.8.2017) + if (oldConfig.exists()) { + if (QDir().rename(oldConfigPath, _bookmarksFilename)) { + qCDebug(interfaceapp) << "Successfully migrated" << AVATAR_ENTITIES_BOOKMARKS_FILENAME; + } else { + qCDebug(interfaceapp) << "Failed to migrate" << AVATAR_ENTITIES_BOOKMARKS_FILENAME; + } + } + + Bookmarks::readFromFile(); +} + +void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { + auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities); + QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection); + _bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks); + _deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarBookmark); + QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection); + + for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) { + addBookmarkToMenu(menubar, it.key(), it.value()); + } + + Bookmarks::sortActions(menubar, _bookmarksMenu); +} + +void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() { +} + +void AvatarEntitiesBookmarks::addBookmark() { +} + +void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) { + QAction* changeAction = _bookmarksMenu->newAction(); + changeAction->setData(bookmark); + connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookedAvatarEntities())); + if (!_isMenuSorted) { + menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); + } else { + // TODO: this is aggressive but other alternatives have proved less fruitful so far. + menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); + Bookmarks::sortActions(menubar, _bookmarksMenu); + } +} diff --git a/interface/src/AvatarEntitiesBookmarks.h b/interface/src/AvatarEntitiesBookmarks.h new file mode 100644 index 0000000000..995b75a88d --- /dev/null +++ b/interface/src/AvatarEntitiesBookmarks.h @@ -0,0 +1,45 @@ +// +// AvatarEntitiesBookmarks.h +// interface/src +// +// Created by Dante Ruiz on 15/01/18. +// 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 +// + +#ifndef hifi_AvatarEntitiesBookmarks_h +#define hifi_AvatarEntitiesBookmarks_h + +#include +#include "Bookmarks.h" + +class AvatarEntitiesBookmarks: public Bookmarks, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + AvatarEntitiesBookmarks(); + void setupMenus(Menu* menubar, MenuWrapper* menu) override; + +public slots: + void addBookmark(); + +protected: + void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override; + void readFromFile() override; + +private: + const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesbookmarks.json"; + const QString AVATAR_ENTITIES_ATTACHMENTS = "AvatarEntitiesName"; + const QString ENTITY_PROPERTIES = "AvatarEntitiesData"; + const QString ENTRY_VERSION = "version"; + + const int ATTACHMENT_BOOKMARK_VERSION = 1; + +private slots: + void applyBookmarkedAvatarEntities(); +}; + +#endif diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3072ecf240..6d18575cbe 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,6 +34,7 @@ #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" #include "AvatarBookmarks.h" +#include "AvatarEntitiesBookmarks.h" #include "devices/DdeFaceTracker.h" #include "MainWindow.h" #include "render/DrawStatus.h" @@ -206,6 +207,9 @@ Menu::Menu() { auto avatarBookmarks = DependencyManager::get(); avatarBookmarks->setupMenus(this, avatarMenu); + auto avatarEntitiesBookmarks = DependencyManager::get(); + avatarBookmarks->setupMenus(this, avatarMenu); + // Display menu ---------------------------------- // FIXME - this is not yet matching Alan's spec because it doesn't have // menus for "2D"/"3D" - we need to add support for detecting the appropriate diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 854f8d8c2b..6dee53a4ed 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -46,9 +46,11 @@ namespace MenuOption { const QString AutoMuteAudio = "Auto Mute Microphone"; const QString AvatarReceiveStats = "Show Receive Stats"; const QString AvatarBookmarks = "Avatar Bookmarks"; + const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks"; const QString Back = "Back"; const QString BinaryEyelidControl = "Binary Eyelid Control"; const QString BookmarkAvatar = "Bookmark Avatar"; + const QString BookmarkAvatarEntities = "Bookmark Avatar Entities"; const QString BookmarkLocation = "Bookmark Location"; const QString CalibrateCamera = "Calibrate Camera"; const QString CameraEntityMode = "Entity Mode"; From 3382a35c3fff97c3897b4e39eff413eb6abff96a Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Tue, 16 Jan 2018 12:13:45 +0100 Subject: [PATCH 20/73] Fixed bug due to incorrect discard of fragments with no local lights --- .../render-utils/src/DeferredGlobalLight.slh | 15 +++++--- .../render-utils/src/LightClusterGrid.slh | 38 ++++++++++++------- libraries/render-utils/src/LightLocal.slh | 31 +++++++++------ .../src/local_lights_drawOutline.slf | 3 ++ .../render-utils/src/local_lights_shading.slf | 5 ++- .../render-utils/src/model_translucent.slf | 17 +++++---- .../src/model_translucent_fade.slf | 17 +++++---- .../src/overlay3D_model_translucent.slf | 2 +- .../src/simple_transparent_textured.slf | 2 +- .../src/simple_transparent_textured_fade.slf | 2 +- 10 files changed, 81 insertions(+), 51 deletions(-) diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index 8406e4a0c5..02ab97b825 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -171,9 +171,11 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur <$declareLightingAmbient(1, 1, 1)$> <$declareLightingDirectional()$> -vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) { +vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity, vec3 prevLighting) { <$prepareGlobalLight()$> + color = prevLighting; + color += emissive * isEmissiveEnabled(); // Ambient @@ -195,25 +197,26 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl vec3 evalGlobalLightingAlphaBlendedWithHaze( mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, - vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) + vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity, vec3 prevLighting) { <$prepareGlobalLight()$> + color = prevLighting; + color += emissive * isEmissiveEnabled(); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance); - color += ambientDiffuse; - color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); - color += directionalDiffuse; - color += directionalSpecular / opacity; + + color += ambientDiffuse + directionalDiffuse; + color += (ambientSpecular + directionalSpecular) / opacity; // Haze if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { diff --git a/libraries/render-utils/src/LightClusterGrid.slh b/libraries/render-utils/src/LightClusterGrid.slh index d2eb213c71..60daa520a3 100644 --- a/libraries/render-utils/src/LightClusterGrid.slh +++ b/libraries/render-utils/src/LightClusterGrid.slh @@ -95,22 +95,32 @@ int clusterGrid_getClusterLightId(int index, int offset) { ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos)); int numLights = cluster.x + cluster.y; - if (numLights <= 0) { - discard; - } - ivec3 dims = frustumGrid.dims.xyz; - if (clusterPos.x < 0 || clusterPos.x >= dims.x) { - discard; - } - - if (clusterPos.y < 0 || clusterPos.y >= dims.y) { - discard; - } - if (clusterPos.z < 0 || clusterPos.z > dims.z) { - discard; - } <@endfunc@> +bool hasLocalLights(int numLights, ivec3 clusterPos, ivec3 dims) { +/* + if (numLights <= 0) { + return false; + } + + if (clusterPos.x < 0 || clusterPos.x >= dims.x) { + return false; + } + + if (clusterPos.y < 0 || clusterPos.y >= dims.y) { + return false; + } + if (clusterPos.z < 0 || clusterPos.z > dims.z) { + return false; + } + return true; +*/ + return numLights>0 + && all(greaterThanEqual(clusterPos, ivec3(0))) + && all(lessThan(clusterPos.xy, dims.xy)) + && clusterPos.z <= dims.z; +} + <@endif@> diff --git a/libraries/render-utils/src/LightLocal.slh b/libraries/render-utils/src/LightLocal.slh index d9980b1fa6..895955269d 100644 --- a/libraries/render-utils/src/LightLocal.slh +++ b/libraries/render-utils/src/LightLocal.slh @@ -22,8 +22,11 @@ vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, vec3 fragNormal, vec3 fragEyeDir, vec4 midNormalCurvature, vec4 lowNormalCurvature, - float fragRoughness, float fragScattering, float fragMetallic, vec3 fragFresnel, vec3 fragAlbedo) { - vec4 _fragColor = vec4(0.0); + float fragRoughness, float fragScattering, float fragMetallic, vec3 fragFresnel, vec3 fragAlbedo, + float opacity) { + vec4 fragColor = vec4(0.0); + vec3 fragSpecular = vec3(0.0); + vec3 fragDiffuse = vec3(0.0); int lightClusterOffset = cluster.z; // Compute the rougness into gloss2 once: @@ -72,11 +75,11 @@ vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, vec3 fra evalFragShadingGloss(diffuse, specular, fragNormal, fragLightDir, fragEyeDir, fragMetallic, fragFresnel, fragGloss2, fragAlbedo); } - diffuse *= lightEnergy * isDiffuseEnabled(); - specular *= lightEnergy * isSpecularEnabled(); + diffuse *= lightEnergy; + specular *= lightEnergy; - _fragColor.rgb += diffuse; - _fragColor.rgb += specular; + fragDiffuse.rgb += diffuse; + fragSpecular.rgb += specular; } for (int i = cluster.x; i < numLights; i++) { @@ -127,11 +130,17 @@ vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, vec3 fra evalFragShadingGloss(diffuse, specular, fragNormal, fragLightDir, fragEyeDir, fragMetallic, fragFresnel, fragGloss2, fragAlbedo); } - diffuse *= lightEnergy * isDiffuseEnabled(); - specular *= lightEnergy * isSpecularEnabled(); + diffuse *= lightEnergy; + specular *= lightEnergy; - _fragColor.rgb += diffuse; - _fragColor.rgb += specular; + fragDiffuse.rgb += diffuse; + fragSpecular.rgb += specular; } - return _fragColor; + + fragDiffuse *= isDiffuseEnabled(); + fragSpecular *= isSpecularEnabled(); + + fragColor.rgb += fragDiffuse; + fragColor.rgb += fragSpecular / opacity; + return fragColor; } \ No newline at end of file diff --git a/libraries/render-utils/src/local_lights_drawOutline.slf b/libraries/render-utils/src/local_lights_drawOutline.slf index f7527d0542..de49478eba 100644 --- a/libraries/render-utils/src/local_lights_drawOutline.slf +++ b/libraries/render-utils/src/local_lights_drawOutline.slf @@ -55,6 +55,9 @@ void main(void) { vec4 fragPos = invViewMat * fragPosition; <$fetchClusterInfo(fragPos)$>; + if (!hasLocalLights(numLights, clusterPos, dims)) { + discard; + } // Frag to eye vec vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); diff --git a/libraries/render-utils/src/local_lights_shading.slf b/libraries/render-utils/src/local_lights_shading.slf index f302c6daba..b9c57b82cb 100644 --- a/libraries/render-utils/src/local_lights_shading.slf +++ b/libraries/render-utils/src/local_lights_shading.slf @@ -41,6 +41,9 @@ void main(void) { vec4 fragWorldPos = invViewMat * fragPosition; <$fetchClusterInfo(fragWorldPos)$>; + if (!hasLocalLights(numLights, clusterPos, dims)) { + discard; + } vec4 midNormalCurvature; vec4 lowNormalCurvature; @@ -55,7 +58,7 @@ void main(void) { _fragColor = evalLocalLighting(cluster, numLights, fragWorldPos.xyz, frag.normal, fragEyeDir, midNormalCurvature, lowNormalCurvature, frag.roughness, frag.scattering, - frag.metallic, frag.fresnel, frag.albedo); + frag.metallic, frag.fresnel, frag.albedo, 1.0); } diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 5f61aea889..c054a5c096 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -68,16 +68,17 @@ void main(void) { vec3 fragNormal = normalize(_normal); TransformCamera cam = getTransformCamera(); + vec4 localLighting = vec4(0.0); <$fetchClusterInfo(_worldPosition)$>; + if (hasLocalLights(numLights, clusterPos, dims)) { + vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); + vec3 fragEyeDir = normalize(fragEyeVector); - vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - - vec4 localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, fragNormal, fragEyeDir, - vec4(0), vec4(0), roughness, 0.0, - metallic, fresnel, albedo); - emissive += localLighting.rgb; + localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, fragNormal, fragEyeDir, + vec4(0), vec4(0), roughness, 0.0, + metallic, fresnel, albedo, opacity); + } _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, @@ -89,6 +90,6 @@ void main(void) { fresnel, metallic, emissive, - roughness, opacity), + roughness, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index e07730a07e..c6bfd1659d 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -77,16 +77,17 @@ void main(void) { vec3 fragNormal = normalize(_normal); TransformCamera cam = getTransformCamera(); + vec4 localLighting = vec4(0.0); <$fetchClusterInfo(_worldPosition)$>; + if (hasLocalLights(numLights, clusterPos, dims)) { + vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); + vec3 fragEyeDir = normalize(fragEyeVector); - vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - - vec4 localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, fragNormal, fragEyeDir, - vec4(0), vec4(0), roughness, 0.0, - metallic, fresnel, albedo); - emissive += localLighting.rgb; + localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, fragNormal, fragEyeDir, + vec4(0), vec4(0), roughness, 0.0, + metallic, fresnel, albedo, opacity); + } _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, @@ -98,6 +99,6 @@ void main(void) { fresnel, metallic, emissive+fadeEmissive, - roughness, opacity), + roughness, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf index b26e70f465..18f40a37b4 100644 --- a/libraries/render-utils/src/overlay3D_model_translucent.slf +++ b/libraries/render-utils/src/overlay3D_model_translucent.slf @@ -75,7 +75,7 @@ void main(void) { fresnel, metallic, emissive, - roughness, opacity), + roughness, opacity, vec3(0)), opacity); // Apply standard tone mapping diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index b16b19c8b4..5068b59e89 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -56,7 +56,7 @@ void main(void) { 0.0, vec3(0.0f), DEFAULT_ROUGHNESS, - opacity), + opacity, vec3(0)), opacity); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index ad260210a7..2fff00849f 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -68,7 +68,7 @@ void main(void) { 0.0f, fadeEmissive, DEFAULT_ROUGHNESS, - opacity), + opacity, vec3(0)), opacity); } \ No newline at end of file From 8fb863e98c259ac16a7cc4dbaaa695229b38ee24 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Tue, 16 Jan 2018 15:57:05 +0100 Subject: [PATCH 21/73] Added SurfaceData mechanism to evalGlobalLight --- .../render-utils/src/DeferredGlobalLight.slh | 60 ++++++++++++++++--- .../render-utils/src/model_translucent.slf | 4 +- .../render-utils/src/model_translucent.slv | 2 +- .../src/model_translucent_fade.slf | 4 +- .../src/model_translucent_fade.slv | 2 +- .../src/overlay3D_model_translucent.slf | 2 +- .../src/simple_transparent_textured.slf | 2 +- .../src/simple_transparent_textured_fade.slf | 2 +- 8 files changed, 60 insertions(+), 18 deletions(-) diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index ab26f95482..7750b62430 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -21,13 +21,7 @@ <@include LightDirectional.slh@> -<@func prepareGlobalLight(isScattering)@> - // prepareGlobalLight - // Transform directions to worldspace - vec3 fragNormal = vec3((normal)); - vec3 fragEyeVector = vec3(invViewMat * vec4(-1.0*position, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - +<@func fetchGlobalLight()@> // Get light Light light = getKeyLight(); LightAmbient lightAmbient = getLightAmbient(); @@ -36,6 +30,16 @@ vec3 lightIrradiance = getLightIrradiance(light); vec3 color = vec3(0.0); +<@endfunc@> + +<@func prepareGlobalLight(isScattering)@> + // prepareGlobalLight + // Transform directions to worldspace + vec3 fragNormal = vec3((normal)); + vec3 fragEyeVector = vec3(invViewMat * vec4(-position, 0.0)); + vec3 fragEyeDir = normalize(fragEyeVector); + + <$fetchGlobalLight()$> <@endfunc@> @@ -202,12 +206,50 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl vec3 evalGlobalLightingAlphaBlendedWithHaze( mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, - vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity, vec3 prevLighting) + vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) { <$prepareGlobalLight()$> SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + color += emissive * isEmissiveEnabled(); + + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance); + color += ambientDiffuse; + color += ambientSpecular / opacity; + + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse; + color += directionalSpecular / opacity; + + // Haze + if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { + vec4 colorV4 = computeHazeColor( + vec4(color, 1.0), // fragment original color + position, // fragment position in eye coordinates + fragEyeVector, // fragment position in world coordinates + invViewMat[3].y, // eye height in world coordinates + lightDirection // keylight direction vector + ); + + color = colorV4.rgb; + } + + return color; +} + +vec3 evalGlobalLightingAlphaBlendedWithHaze( + mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, + vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, SurfaceData surface, float opacity, vec3 prevLighting) +{ + <$fetchGlobalLight()$> + color = prevLighting; color += emissive * isEmissiveEnabled(); @@ -229,7 +271,7 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec4 colorV4 = computeHazeColor( vec4(color, 1.0), // fragment original color position, // fragment position in eye coordinates - fragEyeVector, // fragment position in world coordinates + surface.eyeDir, // fragment eye vector in world coordinates invViewMat[3].y, // eye height in world coordinates lightDirection // keylight direction vector ); diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 9b6913fe1c..6b3fe5991b 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -59,6 +59,7 @@ void main(void) { <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; vec3 fragPosition = _position.xyz; + // Lighting is done in world space vec3 fragNormal = normalize(_normal); TransformCamera cam = getTransformCamera(); @@ -80,11 +81,10 @@ void main(void) { 1.0, occlusionTex, fragPosition, - fragNormal, albedo, fresnel, metallic, emissive, - roughness, opacity, localLighting.rgb), + surface, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/model_translucent.slv b/libraries/render-utils/src/model_translucent.slv index 2fe4c511f0..305aba06c3 100644 --- a/libraries/render-utils/src/model_translucent.slv +++ b/libraries/render-utils/src/model_translucent.slv @@ -40,5 +40,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index ea8774fd4e..46eac17cf7 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -68,6 +68,7 @@ void main(void) { <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; vec3 fragPosition = _position.xyz; + // Lighting is done in world space vec3 fragNormal = normalize(_normal); TransformCamera cam = getTransformCamera(); @@ -89,11 +90,10 @@ void main(void) { 1.0, occlusionTex, fragPosition, - fragNormal, albedo, fresnel, metallic, emissive+fadeEmissive, - roughness, opacity, localLighting.rgb), + surface, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/model_translucent_fade.slv b/libraries/render-utils/src/model_translucent_fade.slv index eaabdd27e8..3fb9ad2cb4 100644 --- a/libraries/render-utils/src/model_translucent_fade.slv +++ b/libraries/render-utils/src/model_translucent_fade.slv @@ -40,5 +40,5 @@ void main(void) { TransformObject obj = getTransformObject(); <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf index 6f1d34926e..b6ae8eb75e 100644 --- a/libraries/render-utils/src/overlay3D_model_translucent.slf +++ b/libraries/render-utils/src/overlay3D_model_translucent.slf @@ -69,7 +69,7 @@ void main(void) { fresnel, metallic, emissive, - roughness, opacity, vec3(0)), + roughness, opacity), opacity); // Apply standard tone mapping diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 5068b59e89..b16b19c8b4 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -56,7 +56,7 @@ void main(void) { 0.0, vec3(0.0f), DEFAULT_ROUGHNESS, - opacity, vec3(0)), + opacity), opacity); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index 2fff00849f..ad260210a7 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -68,7 +68,7 @@ void main(void) { 0.0f, fadeEmissive, DEFAULT_ROUGHNESS, - opacity, vec3(0)), + opacity), opacity); } \ No newline at end of file From 7707e682349895f80e16459492a8c01c66ad6773 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 16 Jan 2018 15:37:03 -0800 Subject: [PATCH 22/73] bookmarks add back avatar-entities --- interface/src/AvatarEntitiesBookmarks.cpp | 106 +++++++++++++++++++++- interface/src/AvatarEntitiesBookmarks.h | 9 +- interface/src/Menu.cpp | 2 +- interface/src/Menu.h | 1 + interface/src/avatar/MyAvatar.cpp | 25 +++++ interface/src/avatar/MyAvatar.h | 2 + 6 files changed, 138 insertions(+), 7 deletions(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index e034aec458..012c0d6f1a 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -18,7 +18,13 @@ #include #include +#include +#include #include +#include +#include +#include +#include #include "MainWindow.h" #include "Menu.h" @@ -29,6 +35,64 @@ #include +void addAvatarEntities(const QVariantList& avatarEntities, const QUuid& avatarSessionID) { + EntityTreePointer entityTree = DependencyManager::get()->getTree(); + if (!entityTree) { + return; + } + EntitySimulationPointer entitySimulation = entityTree->getSimulation(); + PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); + EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); + for (int index = 0; index < avatarEntities.count(); index++) { + qDebug() << "-----------> " << index; + const QVariantList& avatarEntityProperties = avatarEntities.at(index).toList(); + EntityItemProperties entityProperties; + entityProperties.setParentID(avatarSessionID); + entityProperties.setClientOnly(true); + QString typeName = avatarEntityProperties.value(0).toString(); + entityProperties.setType(EntityTypes::getEntityTypeFromName(typeName)); + QString marketplaceID = avatarEntityProperties.value(1).toString(); + entityProperties.setMarketplaceID(marketplaceID); + quint32 editionNumber = (quint32) avatarEntityProperties.value(2).toUInt(); + entityProperties.setEditionNumber(editionNumber); + QString modelURL = avatarEntityProperties.value(3).toString(); + entityProperties.setModelURL(modelURL); + quint16 parentJointIndex = (quint16) avatarEntityProperties.value(4).toUInt(); + glm::vec3 localPosition = vec3FromJsonValue(avatarEntityProperties.value(5).toJsonValue()); + entityProperties.setLocalPosition(localPosition); + glm::quat localRotation = quatFromJsonValue(avatarEntityProperties.value(6).toJsonValue()); + entityProperties.setLocalRotation(localRotation); + + EntityItemID id = EntityItemID(QUuid::createUuid()); + bool success = true; + entityTree->withWriteLock([&] { + EntityItemPointer entity = entityTree->addEntity(id, entityProperties); + if (entity) { + if (entityProperties.queryAACubeRelatedPropertyChanged()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + bool success; + AACube queryAACube = entity->getQueryAACube(success); + if (success) { + entityProperties.setQueryAACube(queryAACube); + } + } + + entity->setLastBroadcast(usecTimestampNow()); + // since we're creating this object we will immediately volunteer to own its simulation + entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); + entityProperties.setLastEdited(entity->getLastEdited()); + } else { + qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; + success = false; + } + }); + + if (success) { + entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties); + } + } +} + AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() { _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME; readFromFile(); @@ -55,7 +119,7 @@ void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities); QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection); _bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks); - _deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarBookmark); + _deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark); QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection); for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) { @@ -66,15 +130,53 @@ void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { } void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() { + qDebug() << "AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities"; + QAction* action = qobject_cast(sender()); + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + const QMap bookmark = action->data().toMap(); + + if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) { + const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); + myAvatar->useFullAvatarURL(avatarUrl); + const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); + addAvatarEntities(avatarEntities, myAvatar->getSelfID()); + const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); + myAvatar->setAvatarScale(avatarScale); + } else { + qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark"; + } } void AvatarEntitiesBookmarks::addBookmark() { + ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "Name", QString()); + connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) { + disconnect(dlg, &ModalDialogListener::response, this, nullptr); + auto bookmarkName = response.toString(); + bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " "); + if (bookmarkName.length() == 0) { + return; + } + + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString(); + const QVariant& avatarScale = myAvatar->getAvatarScale(); + + QVariantMap *bookmark = new QVariantMap; + bookmark->insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION); + bookmark->insert(ENTRY_AVATAR_URL, avatarUrl); + bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale); + bookmark->insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); + + Bookmarks::addBookmarkToFile(bookmarkName, *bookmark); + }); } void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) { QAction* changeAction = _bookmarksMenu->newAction(); changeAction->setData(bookmark); - connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookedAvatarEntities())); + connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities())); if (!_isMenuSorted) { menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); } else { diff --git a/interface/src/AvatarEntitiesBookmarks.h b/interface/src/AvatarEntitiesBookmarks.h index 995b75a88d..12e8c47768 100644 --- a/interface/src/AvatarEntitiesBookmarks.h +++ b/interface/src/AvatarEntitiesBookmarks.h @@ -31,12 +31,13 @@ protected: void readFromFile() override; private: - const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesbookmarks.json"; - const QString AVATAR_ENTITIES_ATTACHMENTS = "AvatarEntitiesName"; - const QString ENTITY_PROPERTIES = "AvatarEntitiesData"; + const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json"; + const QString ENTRY_AVATAR_URL = "AvatarUrl"; + const QString ENTRY_AVATAR_SCALE = "AvatarScale"; + const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities"; const QString ENTRY_VERSION = "version"; - const int ATTACHMENT_BOOKMARK_VERSION = 1; + const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1; private slots: void applyBookmarkedAvatarEntities(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6d18575cbe..14bc53950f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -208,7 +208,7 @@ Menu::Menu() { avatarBookmarks->setupMenus(this, avatarMenu); auto avatarEntitiesBookmarks = DependencyManager::get(); - avatarBookmarks->setupMenus(this, avatarMenu); + avatarEntitiesBookmarks->setupMenus(this, avatarMenu); // Display menu ---------------------------------- // FIXME - this is not yet matching Alan's spec because it doesn't have diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 6dee53a4ed..8c41c66041 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -80,6 +80,7 @@ namespace MenuOption { const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DefaultSkybox = "Default Skybox"; const QString DeleteAvatarBookmark = "Delete Avatar Bookmark..."; + const QString DeleteAvatarEntitiesBookmark = "Delete Avatar Entities Bookmark"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e863a58e14..42b72beb7d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -1419,6 +1421,29 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { } +QVariantList MyAvatar::getAvatarEntitiesVariant() { + QVariantList avatarEntitiesData; + forEachChild([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + auto modelEntity = std::dynamic_pointer_cast(child); + if (modelEntity) { + QVariantList avatarEntityProperties; + EntityItemProperties entityProperties = modelEntity->getProperties(); + avatarEntityProperties.append(QVariant(EntityTypes::getEntityTypeName(entityProperties.getType()))); + avatarEntityProperties.append(QVariant(entityProperties.getMarketplaceID())); + avatarEntityProperties.append(QVariant(entityProperties.getEditionNumber())); + avatarEntityProperties.append(QVariant(entityProperties.getModelURL())); + avatarEntityProperties.append(QVariant(entityProperties.getParentJointIndex())); + avatarEntityProperties.append(QVariant(toJsonValue(entityProperties.getLocalPosition()))); + avatarEntityProperties.append(QVariant(toJsonValue(entityProperties.getLocalRotation()))); + avatarEntityProperties.append(QVariant(entityProperties.getUserData())); + avatarEntitiesData.append(QVariant(avatarEntityProperties)); + } + } + }); + return avatarEntitiesData; +} + void MyAvatar::resetFullAvatarURL() { auto lastAvatarURL = getFullAvatarURLFromPreferences(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ab74460d4e..45a3773e1a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -510,6 +510,8 @@ public: bool hasDriveInput() const; + QVariantList getAvatarEntitiesVariant(); + Q_INVOKABLE bool isFlying(); Q_INVOKABLE bool isInAir(); Q_INVOKABLE void setFlyingEnabled(bool enabled); From 92a259fd63ad4f8c051e20f91f0b2751393453ca Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 16 Jan 2018 16:41:06 -0800 Subject: [PATCH 23/73] fix setting correct joint index --- interface/src/AvatarEntitiesBookmarks.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index 012c0d6f1a..7798c0b84b 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -35,7 +35,9 @@ #include -void addAvatarEntities(const QVariantList& avatarEntities, const QUuid& avatarSessionID) { +void addAvatarEntities(const QVariantList& avatarEntities, std::shared_ptr myAvatar) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); EntityTreePointer entityTree = DependencyManager::get()->getTree(); if (!entityTree) { return; @@ -44,24 +46,25 @@ void addAvatarEntities(const QVariantList& avatarEntities, const QUuid& avatarSe PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); for (int index = 0; index < avatarEntities.count(); index++) { - qDebug() << "-----------> " << index; const QVariantList& avatarEntityProperties = avatarEntities.at(index).toList(); EntityItemProperties entityProperties; - entityProperties.setParentID(avatarSessionID); - entityProperties.setClientOnly(true); + entityProperties.setParentID(myNodeID); QString typeName = avatarEntityProperties.value(0).toString(); entityProperties.setType(EntityTypes::getEntityTypeFromName(typeName)); QString marketplaceID = avatarEntityProperties.value(1).toString(); entityProperties.setMarketplaceID(marketplaceID); - quint32 editionNumber = (quint32) avatarEntityProperties.value(2).toUInt(); + int editionNumber = avatarEntityProperties.value(2).toInt(); entityProperties.setEditionNumber(editionNumber); QString modelURL = avatarEntityProperties.value(3).toString(); entityProperties.setModelURL(modelURL); - quint16 parentJointIndex = (quint16) avatarEntityProperties.value(4).toUInt(); + int parentJointIndex = avatarEntityProperties.value(4).toInt(); + entityProperties.setParentJointIndex(parentJointIndex); glm::vec3 localPosition = vec3FromJsonValue(avatarEntityProperties.value(5).toJsonValue()); entityProperties.setLocalPosition(localPosition); glm::quat localRotation = quatFromJsonValue(avatarEntityProperties.value(6).toJsonValue()); entityProperties.setLocalRotation(localRotation); + QString userData = avatarEntityProperties.value(7).toString(); + entityProperties.setUserData(userData); EntityItemID id = EntityItemID(QUuid::createUuid()); bool success = true; @@ -140,7 +143,7 @@ void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() { const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); - addAvatarEntities(avatarEntities, myAvatar->getSelfID()); + addAvatarEntities(avatarEntities, myAvatar); const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); myAvatar->setAvatarScale(avatarScale); } else { From 744da485512c691ea110145a8bb3a809402cf57e Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 17 Jan 2018 14:00:50 -0500 Subject: [PATCH 24/73] add apache 2.0 headers --- libraries/shared/src/shared/MiniPromises.cpp | 8 ++++++++ libraries/shared/src/shared/MiniPromises.h | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/libraries/shared/src/shared/MiniPromises.cpp b/libraries/shared/src/shared/MiniPromises.cpp index 28e2c18857..faada3627a 100644 --- a/libraries/shared/src/shared/MiniPromises.cpp +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -1,2 +1,10 @@ +// +// Created by Timothy Dedischew on 2017/12/21 +// 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 +// + #include "MiniPromises.h" int MiniPromise::metaTypeID = qRegisterMetaType("MiniPromise::Promise"); diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h index 0222a91d44..3385118666 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -1,3 +1,11 @@ +// +// Created by Timothy Dedischew on 2017/12/21 +// 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 +// + #pragma once // Minimalist threadsafe Promise-like helper for managing asynchronous results From 3abb3d6fcc75c5f6b204d5dd00882b860b5b79bf Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 17 Jan 2018 11:04:32 -0800 Subject: [PATCH 25/73] trying to add offset --- interface/src/AvatarEntitiesBookmarks.cpp | 14 ++++++++++---- interface/src/avatar/MyAvatar.cpp | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index 7798c0b84b..e2de915061 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -59,12 +59,18 @@ void addAvatarEntities(const QVariantList& avatarEntities, std::shared_ptrgetJointPosition(parentJointIndex); + glm::vec3 positionOffset = vec3FromJsonValue(avatarEntityProperties.value(5).toJsonValue()); + glm::vec3 finalPosition = jointPosition + positionOffset; + entityProperties.setLocalPosition(finalPosition); + glm::quat jointRotation = myAvatar->getJointRotation(parentJointIndex); + glm::quat rotationOffset = quatFromJsonValue(avatarEntityProperties.value(6).toJsonValue()); + glm::quat finalRotation = jointRotation * rotationOffset; + entityProperties.setRotation(finalRotation); QString userData = avatarEntityProperties.value(7).toString(); entityProperties.setUserData(userData); + glm::vec3 dimensions = vec3FromJsonValue(avatarEntityProperties.value(8).toJsonValue()); + entityProperties.setDimensions(dimensions); EntityItemID id = EntityItemID(QUuid::createUuid()); bool success = true; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 42b72beb7d..e370054652 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1429,14 +1429,25 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { if (modelEntity) { QVariantList avatarEntityProperties; EntityItemProperties entityProperties = modelEntity->getProperties(); + + // calculate the entity offset from the parentJointIndex + int jointIndex = entityProperties.getParentJointIndex(); + glm::quat jointRotation = getJointRotation(jointIndex); + glm::quat entityRotation = modelEntity->getWorldOrientation(); + glm::vec3 jointPosition = getJointPosition(jointIndex); + glm::vec3 entityPosition = modelEntity->getWorldPosition(); + glm::quat rotationOffset = glm::inverse(jointRotation) * entityRotation; + glm::vec3 positionOffset = entityPosition - jointPosition; + avatarEntityProperties.append(QVariant(EntityTypes::getEntityTypeName(entityProperties.getType()))); avatarEntityProperties.append(QVariant(entityProperties.getMarketplaceID())); avatarEntityProperties.append(QVariant(entityProperties.getEditionNumber())); avatarEntityProperties.append(QVariant(entityProperties.getModelURL())); - avatarEntityProperties.append(QVariant(entityProperties.getParentJointIndex())); - avatarEntityProperties.append(QVariant(toJsonValue(entityProperties.getLocalPosition()))); - avatarEntityProperties.append(QVariant(toJsonValue(entityProperties.getLocalRotation()))); + avatarEntityProperties.append(QVariant(jointIndex)); + avatarEntityProperties.append(QVariant(toJsonValue(positionOffset))); + avatarEntityProperties.append(QVariant(toJsonValue(rotationOffset))); avatarEntityProperties.append(QVariant(entityProperties.getUserData())); + avatarEntityProperties.append(QVariant(toJsonValue(entityProperties.getDimensions()))); avatarEntitiesData.append(QVariant(avatarEntityProperties)); } } From 3ba0eaa13d9906aaf1ef3787e17be4845242190f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 17 Jan 2018 11:05:55 -0800 Subject: [PATCH 26/73] fix typo --- interface/src/AvatarEntitiesBookmarks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index e2de915061..ddc606455d 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -62,7 +62,7 @@ void addAvatarEntities(const QVariantList& avatarEntities, std::shared_ptrgetJointPosition(parentJointIndex); glm::vec3 positionOffset = vec3FromJsonValue(avatarEntityProperties.value(5).toJsonValue()); glm::vec3 finalPosition = jointPosition + positionOffset; - entityProperties.setLocalPosition(finalPosition); + entityProperties.setPosition(finalPosition); glm::quat jointRotation = myAvatar->getJointRotation(parentJointIndex); glm::quat rotationOffset = quatFromJsonValue(avatarEntityProperties.value(6).toJsonValue()); glm::quat finalRotation = jointRotation * rotationOffset; From 0930cc4f0e3bfbcc4b001a08df1eab78521f4f7c Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 17 Jan 2018 17:08:57 -0500 Subject: [PATCH 27/73] partial getMapping PR integration --- .../script-engine/src/AssetScriptingInterface.cpp | 5 ++--- .../script-engine/src/AssetScriptingInterface.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index a7d0bad3c2..54e74564ef 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -27,8 +27,7 @@ #include #include "Gzip.h" #include "ScriptEngine.h" - -//using Promise = MiniPromise::Promise; +#include "ScriptEngineLogging.h" AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) { if (auto engine = qobject_cast(parent)) { @@ -88,7 +87,7 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb if (!urlString.startsWith(ATP_SCHEME)) { // ... for now at least log a message so user can check logs - qDebug() << "AssetScriptingInterface::downloadData ERROR: url does not start with " << ATP_SCHEME; + qCDebug(scriptengine) << "AssetScriptingInterface::downloadData url must be of form atp:"; return; } QString hash = AssetUtils::extractAssetHash(urlString); diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 9022316634..f1a270b6c7 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -79,6 +79,21 @@ public: * @param {string} error */ Q_INVOKABLE void setMapping(QString path, QString hash, QScriptValue callback); + + /**jsdoc + * Look up a path to hash mapping within the connected domain's asset server + * @function Assets.getMapping + * @static + * @param path {string} + * @param callback {Assets~getMappingCallback} + */ + + /**jsdoc + * Called when getMapping is complete. + * @callback Assets~getMappingCallback + * @param error {string} error description if the path could not be resolved; otherwise a null value. + * @param assetID {string} hash value if found, else an empty string + */ Q_INVOKABLE void getMapping(QString path, QScriptValue callback); Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); From eadd3b48bd8d820cfc2bcd3a93f1be6170c2c25a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 17 Jan 2018 15:42:07 -0800 Subject: [PATCH 28/73] bookmark avatar-entities-done --- interface/src/AvatarEntitiesBookmarks.cpp | 40 +++++++++-------------- interface/src/avatar/MyAvatar.cpp | 39 ++++++++++------------ interface/src/avatar/MyAvatar.h | 1 + 3 files changed, 34 insertions(+), 46 deletions(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index ddc606455d..11d0d88368 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "MainWindow.h" #include "Menu.h" @@ -35,7 +36,7 @@ #include -void addAvatarEntities(const QVariantList& avatarEntities, std::shared_ptr myAvatar) { +void addAvatarEntities(const QVariantList& avatarEntities) { auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); EntityTreePointer entityTree = DependencyManager::get()->getTree(); @@ -45,32 +46,20 @@ void addAvatarEntities(const QVariantList& avatarEntities, std::shared_ptrgetSimulation(); PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); + QScriptEngine scriptEngine; for (int index = 0; index < avatarEntities.count(); index++) { - const QVariantList& avatarEntityProperties = avatarEntities.at(index).toList(); + const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap(); + QVariant variantProperties = avatarEntityProperties["properties"]; + QVariantMap asMap = variantProperties.toMap(); + QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); EntityItemProperties entityProperties; + EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties); + entityProperties.setParentID(myNodeID); - QString typeName = avatarEntityProperties.value(0).toString(); - entityProperties.setType(EntityTypes::getEntityTypeFromName(typeName)); - QString marketplaceID = avatarEntityProperties.value(1).toString(); - entityProperties.setMarketplaceID(marketplaceID); - int editionNumber = avatarEntityProperties.value(2).toInt(); - entityProperties.setEditionNumber(editionNumber); - QString modelURL = avatarEntityProperties.value(3).toString(); - entityProperties.setModelURL(modelURL); - int parentJointIndex = avatarEntityProperties.value(4).toInt(); - entityProperties.setParentJointIndex(parentJointIndex); - glm::vec3 jointPosition = myAvatar->getJointPosition(parentJointIndex); - glm::vec3 positionOffset = vec3FromJsonValue(avatarEntityProperties.value(5).toJsonValue()); - glm::vec3 finalPosition = jointPosition + positionOffset; - entityProperties.setPosition(finalPosition); - glm::quat jointRotation = myAvatar->getJointRotation(parentJointIndex); - glm::quat rotationOffset = quatFromJsonValue(avatarEntityProperties.value(6).toJsonValue()); - glm::quat finalRotation = jointRotation * rotationOffset; - entityProperties.setRotation(finalRotation); - QString userData = avatarEntityProperties.value(7).toString(); - entityProperties.setUserData(userData); - glm::vec3 dimensions = vec3FromJsonValue(avatarEntityProperties.value(8).toJsonValue()); - entityProperties.setDimensions(dimensions); + entityProperties.setClientOnly(true); + entityProperties.setOwningAvatarID(myNodeID); + entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY); + entityProperties.markAllChanged(); EntityItemID id = EntityItemID(QUuid::createUuid()); bool success = true; @@ -146,10 +135,11 @@ void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() { const QMap bookmark = action->data().toMap(); if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) { + myAvatar->removeAvatarEntities(); const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); - addAvatarEntities(avatarEntities, myAvatar); + addAvatarEntities(avatarEntities); const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); myAvatar->setAvatarScale(avatarScale); } else { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7998c150d6..a63f1fb279 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1422,34 +1422,31 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { } +void MyAvatar::removeAvatarEntities() { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->deleteEntity(entityID, true, true); + } + }); + } +} + QVariantList MyAvatar::getAvatarEntitiesVariant() { QVariantList avatarEntitiesData; + QScriptEngine scriptEngine; forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { auto modelEntity = std::dynamic_pointer_cast(child); if (modelEntity) { - QVariantList avatarEntityProperties; + QVariantMap avatarEntityData; EntityItemProperties entityProperties = modelEntity->getProperties(); - - // calculate the entity offset from the parentJointIndex - int jointIndex = entityProperties.getParentJointIndex(); - glm::quat jointRotation = getJointRotation(jointIndex); - glm::quat entityRotation = modelEntity->getWorldOrientation(); - glm::vec3 jointPosition = getJointPosition(jointIndex); - glm::vec3 entityPosition = modelEntity->getWorldPosition(); - glm::quat rotationOffset = glm::inverse(jointRotation) * entityRotation; - glm::vec3 positionOffset = entityPosition - jointPosition; - - avatarEntityProperties.append(QVariant(EntityTypes::getEntityTypeName(entityProperties.getType()))); - avatarEntityProperties.append(QVariant(entityProperties.getMarketplaceID())); - avatarEntityProperties.append(QVariant(entityProperties.getEditionNumber())); - avatarEntityProperties.append(QVariant(entityProperties.getModelURL())); - avatarEntityProperties.append(QVariant(jointIndex)); - avatarEntityProperties.append(QVariant(toJsonValue(positionOffset))); - avatarEntityProperties.append(QVariant(toJsonValue(rotationOffset))); - avatarEntityProperties.append(QVariant(entityProperties.getUserData())); - avatarEntityProperties.append(QVariant(toJsonValue(entityProperties.getDimensions()))); - avatarEntitiesData.append(QVariant(avatarEntityProperties)); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + avatarEntityData["properties"] = scriptProperties.toVariant(); + avatarEntitiesData.append(QVariant(avatarEntityData)); } } }); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 45a3773e1a..ad9c227ecf 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -511,6 +511,7 @@ public: bool hasDriveInput() const; QVariantList getAvatarEntitiesVariant(); + void removeAvatarEntities(); Q_INVOKABLE bool isFlying(); Q_INVOKABLE bool isInAir(); From d2a3a71f0036efc8af22051d61cc612d51596f3b Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 18 Jan 2018 21:48:41 -0500 Subject: [PATCH 29/73] trying JS_VERIFY on for size (instead of JS_ASSERT) --- .../src/AssetScriptingInterface.cpp | 39 +++++++++---------- .../src/AssetScriptingInterface.h | 2 +- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 47f338e419..d80491b2a6 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -28,12 +28,9 @@ #include "ScriptEngine.h" #include "ScriptEngineLogging.h" -AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) { - if (auto engine = qobject_cast(parent)) { - registerMetaTypes(engine); - } -} -#define JS_ASSERT(cond, error) { if (!this->jsAssert(cond, error)) { return; } } +AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) {} + +#define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } } void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { auto handler = makeScopedHandlerObject(thisObject(), callback); @@ -176,8 +173,8 @@ void AssetScriptingInterface::sendFakedHandshake() { void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) { auto path = AssetUtils::getATPUrl(asset).path(); auto handler = makeScopedHandlerObject(thisObject(), callback); - JS_ASSERT(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")"); - JS_ASSERT(callback.isFunction(), "expected second parameter to be a callback function"); + JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")"); + JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function"); qDebug() << ">>getMapping//getAssetInfo" << path; Promise promise = getAssetInfo(path); promise->ready([this, handler](QString error, QVariantMap result) { @@ -186,14 +183,14 @@ void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) { }); } -bool AssetScriptingInterface::jsAssert(bool condition, const QString& error) { +bool AssetScriptingInterface::jsVerify(bool condition, const QString& error) { if (condition) { return true; } if (context()) { context()->throwError(error); } else { - qDebug() << "WARNING -- jsAssert failed outside of a valid JS context: " + error; + qDebug() << "WARNING -- jsVerify failed outside of a valid JS context: " + error; } return false; } @@ -202,7 +199,7 @@ void AssetScriptingInterface::jsCallback(const QScriptValue& handler, const QScriptValue& error, const QScriptValue& result) { Q_ASSERT(thread() == QThread::currentThread()); auto errorValue = !error.toBool() ? QScriptValue::NullValue : error; - JS_ASSERT(handler.isObject() && handler.property("callback").isFunction(), + JS_VERIFY(handler.isObject() && handler.property("callback").isFunction(), QString("jsCallback -- .callback is not a function (%1)") .arg(handler.property("callback").toVariant().typeName())); ::callScopedHandlerObject(handler, errorValue, result); @@ -217,11 +214,11 @@ void AssetScriptingInterface::jsCallback(const QScriptValue& handler, } void AssetScriptingInterface::deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { - jsAssert(false, "TODO: deleteAsset API"); + jsVerify(false, "TODO: deleteAsset API"); } void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { - JS_ASSERT(options.isObject() || options.isString(), "expected request options Object or URL as first parameter"); + JS_VERIFY(options.isObject() || options.isString(), "expected request options Object or URL as first parameter"); auto decompress = options.property("decompress").toBool() || options.property("compressed").toBool(); auto responseType = options.property("responseType").toString().toLower(); @@ -235,11 +232,11 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, auto asset = AssetUtils::getATPUrl(url).path(); auto handler = makeScopedHandlerObject(scope, callback); - JS_ASSERT(handler.property("callback").isFunction(), + JS_VERIFY(handler.property("callback").isFunction(), QString("Invalid callback function (%1)").arg(handler.property("callback").toVariant().typeName())); - JS_ASSERT(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset), + JS_VERIFY(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset), QString("Invalid ATP url '%1'").arg(url)); - JS_ASSERT(RESPONSE_TYPES.contains(responseType), + JS_VERIFY(RESPONSE_TYPES.contains(responseType), QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | "))); Promise resolved = makePromise("resolved"); @@ -283,9 +280,9 @@ void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue sc auto asset = AssetUtils::getATPUrl(url).path(); auto handler = makeScopedHandlerObject(scope, callback); - JS_ASSERT(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset), + JS_VERIFY(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset), "expected options to be an asset URL or request options containing .url property"); - JS_ASSERT(handler.property("callback").isFunction(), "invalid callback function"); + JS_VERIFY(handler.property("callback").isFunction(), "invalid callback function"); getAssetInfo(asset)->ready([=](QString error, QVariantMap result) { qDebug() << "//resolveAsset/getAssetInfo" << error << result.value("hash"); jsCallback(handler, error, result); @@ -348,11 +345,11 @@ void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, data.toString().toUtf8() : qscriptvalue_cast(data); - JS_ASSERT(path.isEmpty() || AssetUtils::isValidFilePath(path), + JS_VERIFY(path.isEmpty() || AssetUtils::isValidFilePath(path), QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path)); - JS_ASSERT(handler.property("callback").isFunction(), + JS_VERIFY(handler.property("callback").isFunction(), "invalid callback function"); - JS_ASSERT(dataByteArray.size() > 0, + JS_VERIFY(dataByteArray.size() > 0, QString("expected non-zero .data (got %1 / #%2 bytes)") .arg(data.toVariant().typeName()) .arg(dataByteArray.size())); diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index f1a270b6c7..7001c64634 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -124,7 +124,7 @@ public: protected: void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QVariantMap& result); void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QScriptValue& result); - bool jsAssert(bool condition, const QString& error); + bool jsVerify(bool condition, const QString& error); }; #endif // hifi_AssetScriptingInterface_h From dfb1619dc6ab956f2973371c670fd60a1fb480e4 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 18 Jan 2018 21:49:53 -0500 Subject: [PATCH 30/73] CR feedback; using namespace AssetUtils => inline AssetUtils:: --- assignment-client/src/assets/AssetServer.cpp | 163 +++++++++--------- assignment-client/src/assets/AssetServer.h | 32 ++-- .../src/assets/BakeAssetTask.cpp | 2 +- assignment-client/src/assets/BakeAssetTask.h | 8 +- .../src/assets/SendAssetTask.cpp | 12 +- .../src/assets/UploadAssetTask.cpp | 10 +- .../AssetMappingsScriptingInterface.cpp | 4 +- libraries/networking/src/AssetClient.cpp | 84 +++++---- libraries/networking/src/AssetClient.h | 38 ++-- libraries/networking/src/AssetRequest.cpp | 8 +- .../networking/src/AssetResourceRequest.cpp | 4 +- .../networking/src/AssetResourceRequest.h | 4 +- libraries/networking/src/AssetUpload.cpp | 10 +- libraries/networking/src/MappingRequest.cpp | 66 +++---- libraries/networking/src/MappingRequest.h | 40 ++--- 15 files changed, 238 insertions(+), 247 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 1b533f10f3..0a868737b0 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -39,7 +39,6 @@ #include "SendAssetTask.h" #include "UploadAssetTask.h" - static const uint8_t MIN_CORES_FOR_MULTICORE = 4; static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2; static const uint8_t CPU_AFFINITY_COUNT_LOW = 1; @@ -56,7 +55,7 @@ static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js"; -void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) { +void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) { qDebug() << "Starting bake for: " << assetPath << assetHash; auto it = _pendingBakes.find(assetHash); if (it == _pendingBakes.end()) { @@ -74,23 +73,23 @@ void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPa } } -QString AssetServer::getPathToAssetHash(const AssetHash& assetHash) { +QString AssetServer::getPathToAssetHash(const AssetUtils::AssetHash& assetHash) { return _filesDirectory.absoluteFilePath(assetHash); } -std::pair AssetServer::getAssetStatus(const AssetPath& path, const AssetHash& hash) { +std::pair AssetServer::getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) { auto it = _pendingBakes.find(hash); if (it != _pendingBakes.end()) { - return { (*it)->isBaking() ? Baking : Pending, "" }; + return { (*it)->isBaking() ? AssetUtils::Baking : AssetUtils::Pending, "" }; } - if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { - return { Baked, "" }; + if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) { + return { AssetUtils::Baked, "" }; } auto dotIndex = path.lastIndexOf("."); if (dotIndex == -1) { - return { Irrelevant, "" }; + return { AssetUtils::Irrelevant, "" }; } auto extension = path.mid(dotIndex + 1); @@ -104,16 +103,16 @@ std::pair AssetServer::getAssetStatus(const AssetPath& pa } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; } else { - return { Irrelevant, "" }; + return { AssetUtils::Irrelevant, "" }; } - auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename; + auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename; auto jt = _fileMappings.find(bakedPath); if (jt != _fileMappings.end()) { if (jt->second == hash) { - return { NotBaked, "" }; + return { AssetUtils::NotBaked, "" }; } else { - return { Baked, "" }; + return { AssetUtils::Baked, "" }; } } else { bool loaded; @@ -121,11 +120,11 @@ std::pair AssetServer::getAssetStatus(const AssetPath& pa std::tie(loaded, meta) = readMetaFile(hash); if (loaded && meta.failedLastBake) { - return { Error, meta.lastBakeErrors }; + return { AssetUtils::Error, meta.lastBakeErrors }; } } - return { Pending, "" }; + return { AssetUtils::Pending, "" }; } void AssetServer::bakeAssets() { @@ -137,14 +136,14 @@ void AssetServer::bakeAssets() { } } -void AssetServer::maybeBake(const AssetPath& path, const AssetHash& hash) { +void AssetServer::maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) { if (needsToBeBaked(path, hash)) { qDebug() << "Queuing bake of: " << path; bakeAsset(hash, path, getPathToAssetHash(hash)); } } -void AssetServer::createEmptyMetaFile(const AssetHash& hash) { +void AssetServer::createEmptyMetaFile(const AssetUtils::AssetHash& hash) { QString metaFilePath = "atp:/" + hash + "/meta.json"; QFile metaFile { metaFilePath }; @@ -157,14 +156,14 @@ void AssetServer::createEmptyMetaFile(const AssetHash& hash) { } } -bool AssetServer::hasMetaFile(const AssetHash& hash) { - QString metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json"; +bool AssetServer::hasMetaFile(const AssetUtils::AssetHash& hash) { + QString metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json"; return _fileMappings.find(metaFilePath) != _fileMappings.end(); } -bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHash) { - if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { +bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash) { + if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) { return false; } @@ -196,7 +195,7 @@ bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHa return false; } - auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; + auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; return _fileMappings.find(bakedPath) == _fileMappings.end(); } @@ -235,7 +234,7 @@ AssetServer::AssetServer(ReceivedMessage& message) : ThreadedAssignment(message), _transferTaskPool(this), _bakingTaskPool(this), - _filesizeLimit(MAX_UPLOAD_SIZE) + _filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE) { // store the current state of image compression so we can reset it when this assignment is complete _wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled(); @@ -390,7 +389,7 @@ void AssetServer::completeSetup() { // Check the asset directory to output some information about what we have auto files = _filesDirectory.entryList(QDir::Files); - QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING }; + QRegExp hashFileRegex { AssetUtils::ASSET_HASH_REGEX_STRING }; auto hashedFiles = files.filter(hashFileRegex); qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory."; @@ -410,9 +409,9 @@ void AssetServer::completeSetup() { // get file size limit for an asset static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit"; auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION]; - auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE); + auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(AssetUtils::MAX_UPLOAD_SIZE); - if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) { + if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < AssetUtils::MAX_UPLOAD_SIZE) { _filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS; } @@ -421,7 +420,7 @@ void AssetServer::completeSetup() { } void AssetServer::cleanupUnmappedFiles() { - QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}" }; + QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(AssetUtils::SHA256_HASH_HEX_LENGTH) + "}" }; auto files = _filesDirectory.entryInfoList(QDir::Files); @@ -454,6 +453,8 @@ void AssetServer::cleanupUnmappedFiles() { } void AssetServer::handleAssetMappingOperation(QSharedPointer message, SharedNodePointer senderNode) { + using AssetMappingOperationType = AssetUtils::AssetMappingOperationType; + MessageID messageID; message->readPrimitive(&messageID); @@ -519,7 +520,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode if (!bakedRootFile.isEmpty()) { // we ran into an asset for which we could have a baked version, let's check if it's ready - bakedAssetPath = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile; + bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile; auto bakedIt = _fileMappings.find(bakedAssetPath); if (bakedIt != _fileMappings.end()) { @@ -537,7 +538,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode } } - replyPacket.writePrimitive(AssetServerError::NoError); + replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); if (wasRedirected) { qDebug() << "Writing re-directed hash for" << originalAssetHash << "to" << redirectedAssetHash; @@ -563,12 +564,12 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode } } } else { - replyPacket.writePrimitive(AssetServerError::AssetNotFound); + replyPacket.writePrimitive(AssetUtils::AssetServerError::AssetNotFound); } } void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - replyPacket.writePrimitive(AssetServerError::NoError); + replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); uint32_t count = (uint32_t)_fileMappings.size(); @@ -580,11 +581,11 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN replyPacket.writeString(mapping); replyPacket.write(QByteArray::fromHex(hash.toUtf8())); - BakingStatus status; + AssetUtils::BakingStatus status; QString lastBakeErrors; std::tie(status, lastBakeErrors) = getAssetStatus(mapping, hash); replyPacket.writePrimitive(status); - if (status == Error) { + if (status == AssetUtils::Error) { replyPacket.writeString(lastBakeErrors); } } @@ -594,22 +595,22 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode if (senderNode->getCanWriteToAssetServer()) { QString assetPath = message.readString(); - auto assetHash = message.read(SHA256_HASH_LENGTH).toHex(); + auto assetHash = message.read(AssetUtils::SHA256_HASH_LENGTH).toHex(); // don't process a set mapping operation that is inside the hidden baked folder - if (assetPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { - qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << HIDDEN_BAKED_CONTENT_FOLDER; - replyPacket.writePrimitive(AssetServerError::PermissionDenied); + if (assetPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) { + qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER; + replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied); } else { if (setMapping(assetPath, assetHash)) { - replyPacket.writePrimitive(AssetServerError::NoError); + replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); } else { - replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed); } } } else { - replyPacket.writePrimitive(AssetServerError::PermissionDenied); + replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied); } } @@ -623,21 +624,21 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared for (int i = 0; i < numberOfDeletedMappings; ++i) { auto mapping = message.readString(); - if (!mapping.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + if (!mapping.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) { mappingsToDelete << mapping; } else { qCDebug(asset_server) << "Refusing to delete mapping" << mapping - << "that is inside" << HIDDEN_BAKED_CONTENT_FOLDER; + << "that is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER; } } if (deleteMappings(mappingsToDelete)) { - replyPacket.writePrimitive(AssetServerError::NoError); + replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); } else { - replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed); } } else { - replyPacket.writePrimitive(AssetServerError::PermissionDenied); + replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied); } } @@ -646,20 +647,20 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN QString oldPath = message.readString(); QString newPath = message.readString(); - if (oldPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + if (oldPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) { qCDebug(asset_server) << "Cannot rename" << oldPath << "to" << newPath - << "since one of the paths is inside" << HIDDEN_BAKED_CONTENT_FOLDER; - replyPacket.writePrimitive(AssetServerError::PermissionDenied); + << "since one of the paths is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER; + replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied); } else { if (renameMapping(oldPath, newPath)) { - replyPacket.writePrimitive(AssetServerError::NoError); + replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); } else { - replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed); } } } else { - replyPacket.writePrimitive(AssetServerError::PermissionDenied); + replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied); } } @@ -678,12 +679,12 @@ void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, Shar } if (setBakingEnabled(mappings, enabled)) { - replyPacket.writePrimitive(AssetServerError::NoError); + replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); } else { - replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed); } } else { - replyPacket.writePrimitive(AssetServerError::PermissionDenied); + replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied); } } @@ -691,15 +692,15 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh QByteArray assetHash; MessageID messageID; - if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) { + if (message->getSize() < qint64(AssetUtils::SHA256_HASH_LENGTH + sizeof(messageID))) { qCDebug(asset_server) << "ERROR bad file request"; return; } message->readPrimitive(&messageID); - assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH); + assetHash = message->readWithoutCopy(AssetUtils::SHA256_HASH_LENGTH); - auto size = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(AssetServerError) + sizeof(qint64)); + auto size = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::AssetServerError) + sizeof(qint64)); auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply, size, true); QByteArray hexHash = assetHash.toHex(); @@ -712,11 +713,11 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh if (fileInfo.exists() && fileInfo.isReadable()) { qCDebug(asset_server) << "Opening file: " << fileInfo.filePath(); - replyPacket->writePrimitive(AssetServerError::NoError); + replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError); replyPacket->writePrimitive(fileInfo.size()); } else { qCDebug(asset_server) << "Asset not found: " << QString(hexHash); - replyPacket->writePrimitive(AssetServerError::AssetNotFound); + replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetNotFound); } auto nodeList = DependencyManager::get(); @@ -725,7 +726,7 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh void AssetServer::handleAssetGet(QSharedPointer message, SharedNodePointer senderNode) { - auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset)); + auto minSize = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::DataOffset) + sizeof(AssetUtils::DataOffset)); if (message->getSize() < minSize) { qCDebug(asset_server) << "ERROR bad file request"; @@ -749,14 +750,14 @@ void AssetServer::handleAssetUpload(QSharedPointer message, Sha // for now this also means it isn't allowed to add assets // so return a packet with error that indicates that - auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError), true); + auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetUtils::AssetServerError), true); MessageID messageID; message->readPrimitive(&messageID); // write the message ID and a permission denied error permissionErrorPacket->writePrimitive(messageID); - permissionErrorPacket->writePrimitive(AssetServerError::PermissionDenied); + permissionErrorPacket->writePrimitive(AssetUtils::AssetServerError::PermissionDenied); // send off the packet auto nodeList = DependencyManager::get(); @@ -863,12 +864,12 @@ bool AssetServer::loadMappingsFromFile() { continue; } - if (!isValidFilePath(key)) { + if (!AssetUtils::isValidFilePath(key)) { qCWarning(asset_server) << "Will not keep mapping for" << key << "since it is not a valid path."; continue; } - if (!isValidHash(value.toString())) { + if (!AssetUtils::isValidHash(value.toString())) { qCWarning(asset_server) << "Will not keep mapping for" << key << "since it does not have a valid hash."; continue; } @@ -918,15 +919,15 @@ bool AssetServer::writeMappingsToFile() { return false; } -bool AssetServer::setMapping(AssetPath path, AssetHash hash) { +bool AssetServer::setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash) { path = path.trimmed(); - if (!isValidFilePath(path)) { + if (!AssetUtils::isValidFilePath(path)) { qCWarning(asset_server) << "Cannot set a mapping for invalid path:" << path << "=>" << hash; return false; } - if (!isValidHash(hash)) { + if (!AssetUtils::isValidHash(hash)) { qCWarning(asset_server) << "Cannot set a mapping for invalid hash" << path << "=>" << hash; return false; } @@ -958,23 +959,23 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) { } } -bool pathIsFolder(const AssetPath& path) { +bool pathIsFolder(const AssetUtils::AssetPath& path) { return path.endsWith('/'); } -void AssetServer::removeBakedPathsForDeletedAsset(AssetHash hash) { +void AssetServer::removeBakedPathsForDeletedAsset(AssetUtils::AssetHash hash) { // we deleted the file with this hash // check if we had baked content for that file that should also now be removed // by calling deleteMappings for the hidden baked content folder for this hash - AssetPathList hiddenBakedFolder { HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" }; + AssetUtils::AssetPathList hiddenBakedFolder { AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" }; qCDebug(asset_server) << "Deleting baked content below" << hiddenBakedFolder << "since" << hash << "was deleted"; deleteMappings(hiddenBakedFolder); } -bool AssetServer::deleteMappings(const AssetPathList& paths) { +bool AssetServer::deleteMappings(const AssetUtils::AssetPathList& paths) { // take a copy of the current mappings in case persistence of these deletes fails auto oldMappings = _fileMappings; @@ -1060,11 +1061,11 @@ bool AssetServer::deleteMappings(const AssetPathList& paths) { } } -bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) { +bool AssetServer::renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath) { oldPath = oldPath.trimmed(); newPath = newPath.trimmed(); - if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) { + if (!AssetUtils::isValidFilePath(oldPath) || !AssetUtils::isValidFilePath(newPath)) { qCWarning(asset_server) << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:" << oldPath << "=>" << newPath; @@ -1164,8 +1165,8 @@ static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx"; static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx"; static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js"; -QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) { - return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath; +QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) { + return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath; } void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) { @@ -1197,7 +1198,7 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina qDebug() << "File path: " << filePath; - AssetHash bakedFileHash; + AssetUtils::AssetHash bakedFileHash; if (file.open(QIODevice::ReadOnly)) { QCryptographicHash hasher(QCryptographicHash::Sha256); @@ -1290,8 +1291,8 @@ static const QString BAKE_VERSION_KEY = "bake_version"; static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake"; static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors"; -std::pair AssetServer::readMetaFile(AssetHash hash) { - auto metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json"; +std::pair AssetServer::readMetaFile(AssetUtils::AssetHash hash) { + auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json"; auto it = _fileMappings.find(metaFilePath); if (it == _fileMappings.end()) { @@ -1335,7 +1336,7 @@ std::pair AssetServer::readMetaFile(AssetHash hash) { return { false, {} }; } -bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta) { +bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta) { // construct the JSON that will be in the meta file QJsonObject metaFileObject; @@ -1349,7 +1350,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me auto metaFileJSON = metaFileDoc.toJson(); // get a hash for the contents of the meta-file - AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex(); + AssetUtils::AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex(); // create the meta file in our files folder, named by the hash of its contents QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash)); @@ -1359,7 +1360,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me metaFile.close(); // add a mapping to the meta file so it doesn't get deleted because it is unmapped - auto metaFileMapping = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json"; + auto metaFileMapping = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json"; return setMapping(metaFileMapping, metaFileHash); } else { @@ -1367,7 +1368,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me } } -bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) { +bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled) { for (const auto& path : paths) { auto it = _fileMappings.find(path); if (it != _fileMappings.end()) { diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 7db37860b9..5e7a3c1700 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -21,8 +21,6 @@ #include "AssetUtils.h" #include "ReceivedMessage.h" -using namespace AssetUtils; - namespace std { template <> struct hash { @@ -76,29 +74,29 @@ private: bool writeMappingsToFile(); /// Set the mapping for path to hash - bool setMapping(AssetPath path, AssetHash hash); + bool setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash); /// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`. - bool deleteMappings(const AssetPathList& paths); + bool deleteMappings(const AssetUtils::AssetPathList& paths); /// Rename mapping from `oldPath` to `newPath`. Returns true if successful - bool renameMapping(AssetPath oldPath, AssetPath newPath); + bool renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath); - bool setBakingEnabled(const AssetPathList& paths, bool enabled); + bool setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled); /// Delete any unmapped files from the local asset directory void cleanupUnmappedFiles(); - QString getPathToAssetHash(const AssetHash& assetHash); + QString getPathToAssetHash(const AssetUtils::AssetHash& assetHash); - std::pair getAssetStatus(const AssetPath& path, const AssetHash& hash); + std::pair getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash); void bakeAssets(); - void maybeBake(const AssetPath& path, const AssetHash& hash); - void createEmptyMetaFile(const AssetHash& hash); - bool hasMetaFile(const AssetHash& hash); - bool needsToBeBaked(const AssetPath& path, const AssetHash& assetHash); - void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath); + void maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash); + void createEmptyMetaFile(const AssetUtils::AssetHash& hash); + bool hasMetaFile(const AssetUtils::AssetHash& hash); + bool needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash); + void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath); /// Move baked content for asset to baked directory and update baked status void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir, @@ -107,11 +105,11 @@ private: void handleAbortedBake(QString originalAssetHash, QString assetPath); /// Create meta file to describe baked content for original asset - std::pair readMetaFile(AssetHash hash); - bool writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta()); + std::pair readMetaFile(AssetUtils::AssetHash hash); + bool writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta()); /// Remove baked paths when the original asset is deleteds - void removeBakedPathsForDeletedAsset(AssetHash originalAssetHash); + void removeBakedPathsForDeletedAsset(AssetUtils::AssetHash originalAssetHash); Mappings _fileMappings; @@ -121,7 +119,7 @@ private: /// Task pool for handling uploads and downloads of assets QThreadPool _transferTaskPool; - QHash> _pendingBakes; + QHash> _pendingBakes; QThreadPool _bakingTaskPool; bool _wasColorTextureCompressionEnabled { false }; diff --git a/assignment-client/src/assets/BakeAssetTask.cpp b/assignment-client/src/assets/BakeAssetTask.cpp index 49322ca4cb..5b9add7467 100644 --- a/assignment-client/src/assets/BakeAssetTask.cpp +++ b/assignment-client/src/assets/BakeAssetTask.cpp @@ -24,7 +24,7 @@ static const int OVEN_STATUS_CODE_ABORT { 2 }; std::once_flag registerMetaTypesFlag; -BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) : +BakeAssetTask::BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) : _assetHash(assetHash), _assetPath(assetPath), _filePath(filePath) diff --git a/assignment-client/src/assets/BakeAssetTask.h b/assignment-client/src/assets/BakeAssetTask.h index 8fe93a85e3..7b003fa1bd 100644 --- a/assignment-client/src/assets/BakeAssetTask.h +++ b/assignment-client/src/assets/BakeAssetTask.h @@ -22,12 +22,10 @@ #include -using namespace AssetUtils; - class BakeAssetTask : public QObject, public QRunnable { Q_OBJECT public: - BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath); + BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath); bool isBaking() { return _isBaking.load(); } @@ -43,8 +41,8 @@ signals: private: std::atomic _isBaking { false }; - AssetHash _assetHash; - AssetPath _assetPath; + AssetUtils::AssetHash _assetHash; + AssetUtils::AssetPath _assetPath; QString _filePath; std::unique_ptr _ovenProcess { nullptr }; std::atomic _wasAborted { false }; diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp index eab88e0d46..6da092357f 100644 --- a/assignment-client/src/assets/SendAssetTask.cpp +++ b/assignment-client/src/assets/SendAssetTask.cpp @@ -40,7 +40,7 @@ void SendAssetTask::run() { ByteRange byteRange; _message->readPrimitive(&messageID); - QByteArray assetHash = _message->read(SHA256_HASH_LENGTH); + QByteArray assetHash = _message->read(AssetUtils::SHA256_HASH_LENGTH); // `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`. // `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data, @@ -61,7 +61,7 @@ void SendAssetTask::run() { replyPacketList->writePrimitive(messageID); if (!byteRange.isValid()) { - replyPacketList->writePrimitive(AssetServerError::InvalidByteRange); + replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange); } else { QString filePath = _resourcesDir.filePath(QString(hexHash)); @@ -75,7 +75,7 @@ void SendAssetTask::run() { // check if we're being asked to read data that we just don't have // because of the file size if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) { - replyPacketList->writePrimitive(AssetServerError::InvalidByteRange); + replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange); qCDebug(networking) << "Bad byte range: " << hexHash << " " << byteRange.fromInclusive << ":" << byteRange.toExclusive; } else { @@ -86,7 +86,7 @@ void SendAssetTask::run() { // this range is positive, meaning we just need to seek into the file and then read from there file.seek(byteRange.fromInclusive); - replyPacketList->writePrimitive(AssetServerError::NoError); + replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError); replyPacketList->writePrimitive(size); replyPacketList->write(file.read(size)); } else { @@ -95,7 +95,7 @@ void SendAssetTask::run() { // seek to the part of the file where the negative range begins file.seek(file.size() + byteRange.fromInclusive); - replyPacketList->writePrimitive(AssetServerError::NoError); + replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError); replyPacketList->writePrimitive(size); // first write everything from the negative range to the end of the file @@ -107,7 +107,7 @@ void SendAssetTask::run() { file.close(); } else { qCDebug(networking) << "Asset not found: " << filePath << "(" << hexHash << ")"; - replyPacketList->writePrimitive(AssetServerError::AssetNotFound); + replyPacketList->writePrimitive(AssetUtils::AssetServerError::AssetNotFound); } } diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 238572c451..68bf4db5fd 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -20,8 +20,6 @@ #include "ClientServerUtils.h" -using namespace AssetUtils; - UploadAssetTask::UploadAssetTask(QSharedPointer receivedMessage, SharedNodePointer senderNode, const QDir& resourcesDir, uint64_t filesizeLimit) : _receivedMessage(receivedMessage), @@ -51,7 +49,7 @@ void UploadAssetTask::run() { replyPacket->writePrimitive(messageID); if (fileSize > _filesizeLimit) { - replyPacket->writePrimitive(AssetServerError::AssetTooLarge); + replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetTooLarge); } else { QByteArray fileData = buffer.read(fileSize); @@ -72,7 +70,7 @@ void UploadAssetTask::run() { existingCorrectFile = true; - replyPacket->writePrimitive(AssetServerError::NoError); + replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError); replyPacket->write(hash); } else { qDebug() << "Overwriting an existing file whose contents did not match the expected hash: " << hexHash; @@ -85,7 +83,7 @@ void UploadAssetTask::run() { qDebug() << "Wrote file" << hexHash << "to disk. Upload complete"; file.close(); - replyPacket->writePrimitive(AssetServerError::NoError); + replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError); replyPacket->write(hash); } else { qWarning() << "Failed to upload or write to file" << hexHash << " - upload failed."; @@ -97,7 +95,7 @@ void UploadAssetTask::run() { qWarning() << "Removal of failed upload file" << hexHash << "failed."; } - replyPacket->writePrimitive(AssetServerError::FileOperationFailed); + replyPacket->writePrimitive(AssetUtils::AssetServerError::FileOperationFailed); } } diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 72b6f401e9..211545bf05 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -263,7 +263,7 @@ void AssetMappingModel::refresh() { for (auto& mapping : mappings) { auto& path = mapping.first; - if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) { // Hide baked mappings continue; } @@ -306,7 +306,7 @@ void AssetMappingModel::refresh() { auto statusString = isFolder ? "--" : bakingStatusToString(mapping.second.status); lastItem->setData(statusString, Qt::UserRole + 5); lastItem->setData(mapping.second.bakingErrors, Qt::UserRole + 6); - if (mapping.second.status == Pending) { + if (mapping.second.status == AssetUtils::Pending) { ++numPendingBakes; } } diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index be58c0384c..6980621219 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -34,8 +34,6 @@ MessageID AssetClient::_currentID = 0; -using AssetUtils::AssetMappingOperationType; - AssetClient::AssetClient() { _cacheDir = qApp->property(hifi::properties::APP_LOCAL_DATA_PATH).toString(); setCustomDeleter([](Dependency* dependency){ @@ -140,7 +138,7 @@ void AssetClient::handleAssetMappingOperationReply(QSharedPointerreadPrimitive(&messageID); - AssetServerError error; + AssetUtils::AssetServerError error; message->readPrimitive(&error); // Check if we have any pending requests for this node @@ -176,7 +174,7 @@ bool haveAssetServer() { return true; } -GetMappingRequest* AssetClient::createGetMappingRequest(const AssetPath& path) { +GetMappingRequest* AssetClient::createGetMappingRequest(const AssetUtils::AssetPath& path) { auto request = new GetMappingRequest(path); request->moveToThread(thread()); @@ -192,7 +190,7 @@ GetAllMappingsRequest* AssetClient::createGetAllMappingsRequest() { return request; } -DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetPathList& paths) { +DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetUtils::AssetPathList& paths) { auto request = new DeleteMappingsRequest(paths); request->moveToThread(thread()); @@ -200,7 +198,7 @@ DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetPathL return request; } -SetMappingRequest* AssetClient::createSetMappingRequest(const AssetPath& path, const AssetHash& hash) { +SetMappingRequest* AssetClient::createSetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) { auto request = new SetMappingRequest(path, hash); request->moveToThread(thread()); @@ -208,7 +206,7 @@ SetMappingRequest* AssetClient::createSetMappingRequest(const AssetPath& path, c return request; } -RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) { +RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath) { auto request = new RenameMappingRequest(oldPath, newPath); request->moveToThread(thread()); @@ -216,7 +214,7 @@ RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& o return request; } -SetBakingEnabledRequest* AssetClient::createSetBakingEnabledRequest(const AssetPathList& path, bool enabled) { +SetBakingEnabledRequest* AssetClient::createSetBakingEnabledRequest(const AssetUtils::AssetPathList& path, bool enabled) { auto bakingEnabledRequest = new SetBakingEnabledRequest(path, enabled); bakingEnabledRequest->moveToThread(thread()); @@ -224,7 +222,7 @@ SetBakingEnabledRequest* AssetClient::createSetBakingEnabledRequest(const AssetP return bakingEnabledRequest; } -AssetRequest* AssetClient::createRequest(const AssetHash& hash, const ByteRange& byteRange) { +AssetRequest* AssetClient::createRequest(const AssetUtils::AssetHash& hash, const ByteRange& byteRange) { auto request = new AssetRequest(hash, byteRange); // Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case) @@ -249,7 +247,7 @@ AssetUpload* AssetClient::createUpload(const QByteArray& data) { return upload; } -MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end, +MessageID AssetClient::getAsset(const QString& hash, AssetUtils::DataOffset start, AssetUtils::DataOffset end, ReceivedAssetCallback callback, ProgressCallback progressCallback) { Q_ASSERT(QThread::currentThread() == thread()); @@ -284,7 +282,7 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse } } - callback(false, AssetServerError::NoError, QByteArray()); + callback(false, AssetUtils::AssetServerError::NoError, QByteArray()); return INVALID_MESSAGE_ID; } @@ -310,7 +308,7 @@ MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callbac } } - callback(false, AssetServerError::NoError, { "", 0 }); + callback(false, AssetUtils::AssetServerError::NoError, { "", 0 }); return INVALID_MESSAGE_ID; } @@ -321,12 +319,12 @@ void AssetClient::handleAssetGetInfoReply(QSharedPointer messag message->readPrimitive(&messageID); auto assetHash = message->read(AssetUtils::SHA256_HASH_LENGTH); - AssetServerError error; + AssetUtils::AssetServerError error; message->readPrimitive(&error); AssetInfo info { assetHash.toHex(), 0 }; - if (error == AssetServerError::NoError) { + if (error == AssetUtils::AssetServerError::NoError) { message->readPrimitive(&info.size); } @@ -359,10 +357,10 @@ void AssetClient::handleAssetGetReply(QSharedPointer message, S MessageID messageID; message->readHeadPrimitive(&messageID); - AssetServerError error; + AssetUtils::AssetServerError error; message->readHeadPrimitive(&error); - DataOffset length = 0; + AssetUtils::DataOffset length = 0; if (!error) { message->readHeadPrimitive(&length); } else { @@ -415,7 +413,7 @@ void AssetClient::handleAssetGetReply(QSharedPointer message, S } void AssetClient::handleProgressCallback(const QWeakPointer& node, MessageID messageID, - qint64 size, DataOffset length) { + qint64 size, AssetUtils::DataOffset length) { auto senderNode = node.toStrongRef(); if (!senderNode) { @@ -441,7 +439,7 @@ void AssetClient::handleProgressCallback(const QWeakPointer& node, Message callbacks.progressCallback(size, length); } -void AssetClient::handleCompleteCallback(const QWeakPointer& node, MessageID messageID, DataOffset length) { +void AssetClient::handleCompleteCallback(const QWeakPointer& node, MessageID messageID, AssetUtils::DataOffset length) { auto senderNode = node.toStrongRef(); if (!senderNode) { @@ -475,9 +473,9 @@ void AssetClient::handleCompleteCallback(const QWeakPointer& node, Message } if (message->failed() || length != message->getBytesLeftToRead()) { - callbacks.completeCallback(false, AssetServerError::NoError, QByteArray()); + callbacks.completeCallback(false, AssetUtils::AssetServerError::NoError, QByteArray()); } else { - callbacks.completeCallback(true, AssetServerError::NoError, message->readAll()); + callbacks.completeCallback(true, AssetUtils::AssetServerError::NoError, message->readAll()); } // We should never get to this point without the associated senderNode and messageID @@ -488,7 +486,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer& node, Message } -MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallback callback) { +MessageID AssetClient::getAssetMapping(const AssetUtils::AssetPath& path, MappingOperationCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); auto nodeList = DependencyManager::get(); @@ -500,7 +498,7 @@ MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCa auto messageID = ++_currentID; packetList->writePrimitive(messageID); - packetList->writePrimitive(AssetMappingOperationType::Get); + packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Get); packetList->writeString(path); @@ -511,7 +509,7 @@ MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCa } } - callback(false, AssetServerError::NoError, QSharedPointer()); + callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); return INVALID_MESSAGE_ID; } @@ -527,7 +525,7 @@ MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) { auto messageID = ++_currentID; packetList->writePrimitive(messageID); - packetList->writePrimitive(AssetMappingOperationType::GetAll); + packetList->writePrimitive(AssetUtils::AssetMappingOperationType::GetAll); if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { _pendingMappingRequests[assetServer][messageID] = callback; @@ -536,11 +534,11 @@ MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) { } } - callback(false, AssetServerError::NoError, QSharedPointer()); + callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); return INVALID_MESSAGE_ID; } -MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) { +MessageID AssetClient::deleteAssetMappings(const AssetUtils::AssetPathList& paths, MappingOperationCallback callback) { auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); @@ -550,7 +548,7 @@ MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOp auto messageID = ++_currentID; packetList->writePrimitive(messageID); - packetList->writePrimitive(AssetMappingOperationType::Delete); + packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Delete); packetList->writePrimitive(int(paths.size())); @@ -565,11 +563,11 @@ MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOp } } - callback(false, AssetServerError::NoError, QSharedPointer()); + callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); return INVALID_MESSAGE_ID; } -MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) { +MessageID AssetClient::setAssetMapping(const QString& path, const AssetUtils::AssetHash& hash, MappingOperationCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); auto nodeList = DependencyManager::get(); @@ -581,7 +579,7 @@ MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& has auto messageID = ++_currentID; packetList->writePrimitive(messageID); - packetList->writePrimitive(AssetMappingOperationType::Set); + packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Set); packetList->writeString(path); packetList->write(QByteArray::fromHex(hash.toUtf8())); @@ -593,11 +591,11 @@ MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& has } } - callback(false, AssetServerError::NoError, QSharedPointer()); + callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); return INVALID_MESSAGE_ID; } -MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) { +MessageID AssetClient::renameAssetMapping(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath, MappingOperationCallback callback) { auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); @@ -607,7 +605,7 @@ MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetP auto messageID = ++_currentID; packetList->writePrimitive(messageID); - packetList->writePrimitive(AssetMappingOperationType::Rename); + packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Rename); packetList->writeString(oldPath); packetList->writeString(newPath); @@ -620,11 +618,11 @@ MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetP } } - callback(false, AssetServerError::NoError, QSharedPointer()); + callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); return INVALID_MESSAGE_ID; } -MessageID AssetClient::setBakingEnabled(const AssetPathList& paths, bool enabled, MappingOperationCallback callback) { +MessageID AssetClient::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled, MappingOperationCallback callback) { auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); @@ -634,7 +632,7 @@ MessageID AssetClient::setBakingEnabled(const AssetPathList& paths, bool enabled auto messageID = ++_currentID; packetList->writePrimitive(messageID); - packetList->writePrimitive(AssetMappingOperationType::SetBakingEnabled); + packetList->writePrimitive(AssetUtils::AssetMappingOperationType::SetBakingEnabled); packetList->writePrimitive(enabled); @@ -651,7 +649,7 @@ MessageID AssetClient::setBakingEnabled(const AssetPathList& paths, bool enabled } } - callback(false, AssetServerError::NoError, QSharedPointer()); + callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); return INVALID_MESSAGE_ID; } @@ -735,7 +733,7 @@ MessageID AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback } } - callback(false, AssetServerError::NoError, QString()); + callback(false, AssetUtils::AssetServerError::NoError, QString()); return INVALID_MESSAGE_ID; } @@ -745,7 +743,7 @@ void AssetClient::handleAssetUploadReply(QSharedPointer message MessageID messageID; message->readPrimitive(&messageID); - AssetServerError error; + AssetUtils::AssetServerError error; message->readPrimitive(&error); QString hashString; @@ -792,7 +790,7 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { auto messageMapIt = _pendingUploads.find(node); if (messageMapIt != _pendingUploads.end()) { for (const auto& value : messageMapIt->second) { - value.second(false, AssetServerError::NoError, ""); + value.second(false, AssetUtils::AssetServerError::NoError, ""); } messageMapIt->second.clear(); } @@ -824,7 +822,7 @@ void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) { disconnect(message.data(), nullptr, this, nullptr); } - value.second.completeCallback(false, AssetServerError::NoError, QByteArray()); + value.second.completeCallback(false, AssetUtils::AssetServerError::NoError, QByteArray()); } messageMapIt->second.clear(); } @@ -835,7 +833,7 @@ void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) { if (messageMapIt != _pendingInfoRequests.end()) { AssetInfo info { "", 0 }; for (const auto& value : messageMapIt->second) { - value.second(false, AssetServerError::NoError, info); + value.second(false, AssetUtils::AssetServerError::NoError, info); } messageMapIt->second.clear(); } @@ -845,7 +843,7 @@ void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) { auto messageMapIt = _pendingMappingRequests.find(node); if (messageMapIt != _pendingMappingRequests.end()) { for (const auto& value : messageMapIt->second) { - value.second(false, AssetServerError::NoError, QSharedPointer()); + value.second(false, AssetUtils::AssetServerError::NoError, QSharedPointer()); } messageMapIt->second.clear(); } diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 6f55866740..c10ecf78a3 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -42,12 +42,10 @@ struct AssetInfo { int64_t size; }; -using namespace AssetUtils; - -using MappingOperationCallback = std::function message)>; -using ReceivedAssetCallback = std::function; -using GetInfoCallback = std::function; -using UploadResultCallback = std::function; +using MappingOperationCallback = std::function message)>; +using ReceivedAssetCallback = std::function; +using GetInfoCallback = std::function; +using UploadResultCallback = std::function; using ProgressCallback = std::function; class AssetClient : public QObject, public Dependency { @@ -55,13 +53,13 @@ class AssetClient : public QObject, public Dependency { public: AssetClient(); - Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetPath& path); + Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetUtils::AssetPath& path); Q_INVOKABLE GetAllMappingsRequest* createGetAllMappingsRequest(); - Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths); - Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash); - Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath); - Q_INVOKABLE SetBakingEnabledRequest* createSetBakingEnabledRequest(const AssetPathList& path, bool enabled); - Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash, const ByteRange& byteRange = ByteRange()); + Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetUtils::AssetPathList& paths); + Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash); + Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath); + Q_INVOKABLE SetBakingEnabledRequest* createSetBakingEnabledRequest(const AssetUtils::AssetPathList& path, bool enabled); + Q_INVOKABLE AssetRequest* createRequest(const AssetUtils::AssetHash& hash, const ByteRange& byteRange = ByteRange()); Q_INVOKABLE AssetUpload* createUpload(const QString& filename); Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data); @@ -82,15 +80,15 @@ private slots: void handleNodeClientConnectionReset(SharedNodePointer node); private: - MessageID getAssetMapping(const AssetHash& hash, MappingOperationCallback callback); + MessageID getAssetMapping(const AssetUtils::AssetHash& hash, MappingOperationCallback callback); MessageID getAllAssetMappings(MappingOperationCallback callback); - MessageID setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback); - MessageID deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback); - MessageID renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback); - MessageID setBakingEnabled(const AssetPathList& paths, bool enabled, MappingOperationCallback callback); + MessageID setAssetMapping(const QString& path, const AssetUtils::AssetHash& hash, MappingOperationCallback callback); + MessageID deleteAssetMappings(const AssetUtils::AssetPathList& paths, MappingOperationCallback callback); + MessageID renameAssetMapping(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath, MappingOperationCallback callback); + MessageID setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled, MappingOperationCallback callback); MessageID getAssetInfo(const QString& hash, GetInfoCallback callback); - MessageID getAsset(const QString& hash, DataOffset start, DataOffset end, + MessageID getAsset(const QString& hash, AssetUtils::DataOffset start, AssetUtils::DataOffset end, ReceivedAssetCallback callback, ProgressCallback progressCallback); MessageID uploadAsset(const QByteArray& data, UploadResultCallback callback); @@ -99,8 +97,8 @@ private: bool cancelGetAssetRequest(MessageID id); bool cancelUploadAssetRequest(MessageID id); - void handleProgressCallback(const QWeakPointer& node, MessageID messageID, qint64 size, DataOffset length); - void handleCompleteCallback(const QWeakPointer& node, MessageID messageID, DataOffset length); + void handleProgressCallback(const QWeakPointer& node, MessageID messageID, qint64 size, AssetUtils::DataOffset length); + void handleCompleteCallback(const QWeakPointer& node, MessageID messageID, AssetUtils::DataOffset length); void forceFailureOfPendingRequests(SharedNodePointer node); diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index a036a28cfd..c42e9aff14 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -80,7 +80,7 @@ void AssetRequest::start() { auto hash = _hash; _assetRequestID = assetClient->getAsset(_hash, _byteRange.fromInclusive, _byteRange.toExclusive, - [this, that, hash](bool responseReceived, AssetServerError serverError, const QByteArray& data) { + [this, that, hash](bool responseReceived, AssetUtils::AssetServerError serverError, const QByteArray& data) { if (!that) { qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error; @@ -91,12 +91,12 @@ void AssetRequest::start() { if (!responseReceived) { _error = NetworkError; - } else if (serverError != AssetServerError::NoError) { + } else if (serverError != AssetUtils::AssetServerError::NoError) { switch (serverError) { - case AssetServerError::AssetNotFound: + case AssetUtils::AssetServerError::AssetNotFound: _error = NotFound; break; - case AssetServerError::InvalidByteRange: + case AssetUtils::AssetServerError::InvalidByteRange: _error = InvalidByteRange; break; default: diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index e1a155a561..9523a9aa26 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -68,7 +68,7 @@ void AssetResourceRequest::doSend() { } } -void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { +void AssetResourceRequest::requestMappingForPath(const AssetUtils::AssetPath& path) { auto statTracker = DependencyManager::get(); statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_STARTED); @@ -140,7 +140,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) { _assetMappingRequest->start(); } -void AssetResourceRequest::requestHash(const AssetHash& hash) { +void AssetResourceRequest::requestHash(const AssetUtils::AssetHash& hash) { // Make request to atp auto assetClient = DependencyManager::get(); _assetRequest = assetClient->createRequest(hash, _byteRange); diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index ac36c83985..2fe79040ca 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -34,8 +34,8 @@ private slots: private: static bool urlIsAssetHash(const QUrl& url); - void requestMappingForPath(const AssetPath& path); - void requestHash(const AssetHash& hash); + void requestMappingForPath(const AssetUtils::AssetPath& path); + void requestHash(const AssetUtils::AssetHash& hash); GetMappingRequest* _assetMappingRequest { nullptr }; AssetRequest* _assetRequest { nullptr }; diff --git a/libraries/networking/src/AssetUpload.cpp b/libraries/networking/src/AssetUpload.cpp index fabdd1febc..f1c84e474a 100644 --- a/libraries/networking/src/AssetUpload.cpp +++ b/libraries/networking/src/AssetUpload.cpp @@ -81,21 +81,21 @@ void AssetUpload::start() { qCDebug(asset_client) << "Attempting to upload" << _filename << "to asset-server."; } - assetClient->uploadAsset(_data, [this](bool responseReceived, AssetServerError error, const QString& hash){ + assetClient->uploadAsset(_data, [this](bool responseReceived, AssetUtils::AssetServerError error, const QString& hash){ if (!responseReceived) { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; - case AssetServerError::AssetTooLarge: + case AssetUtils::AssetServerError::AssetTooLarge: _error = TooLarge; break; - case AssetServerError::PermissionDenied: + case AssetUtils::AssetServerError::PermissionDenied: _error = PermissionDenied; break; - case AssetServerError::FileOperationFailed: + case AssetUtils::AssetServerError::FileOperationFailed: _error = ServerFileError; break; default: diff --git a/libraries/networking/src/MappingRequest.cpp b/libraries/networking/src/MappingRequest.cpp index a79105e3ab..07639d4994 100644 --- a/libraries/networking/src/MappingRequest.cpp +++ b/libraries/networking/src/MappingRequest.cpp @@ -51,13 +51,13 @@ QString MappingRequest::getErrorString() const { } } -GetMappingRequest::GetMappingRequest(const AssetPath& path) : _path(path.trimmed()) { +GetMappingRequest::GetMappingRequest(const AssetUtils::AssetPath& path) : _path(path.trimmed()) { }; void GetMappingRequest::doStart() { // short circuit the request if the path is invalid - if (!isValidFilePath(_path)) { + if (!AssetUtils::isValidFilePath(_path)) { _error = MappingRequest::InvalidPath; emit finished(this); return; @@ -66,17 +66,17 @@ void GetMappingRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->getAssetMapping(_path, - [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer message) { + [this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer message) { _mappingRequestID = INVALID_MESSAGE_ID; if (!responseReceived) { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; - case AssetServerError::AssetNotFound: + case AssetUtils::AssetServerError::AssetNotFound: _error = NotFound; break; default: @@ -86,7 +86,7 @@ void GetMappingRequest::doStart() { } if (!_error) { - _hash = message->read(SHA256_HASH_LENGTH).toHex(); + _hash = message->read(AssetUtils::SHA256_HASH_LENGTH).toHex(); // check the boolean to see if this request got re-directed quint8 wasRedirected; @@ -112,7 +112,7 @@ GetAllMappingsRequest::GetAllMappingsRequest() { void GetAllMappingsRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->getAllAssetMappings( - [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer message) { + [this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer message) { _mappingRequestID = INVALID_MESSAGE_ID; @@ -120,7 +120,7 @@ void GetAllMappingsRequest::doStart() { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; default: @@ -135,11 +135,11 @@ void GetAllMappingsRequest::doStart() { message->readPrimitive(&numberOfMappings); for (uint32_t i = 0; i < numberOfMappings; ++i) { auto path = message->readString(); - auto hash = message->read(SHA256_HASH_LENGTH).toHex(); - BakingStatus status; + auto hash = message->read(AssetUtils::SHA256_HASH_LENGTH).toHex(); + AssetUtils::BakingStatus status; QString lastBakeErrors; message->readPrimitive(&status); - if (status == BakingStatus::Error) { + if (status == AssetUtils::BakingStatus::Error) { lastBakeErrors = message->readString(); } _mappings[path] = { hash, status, lastBakeErrors }; @@ -149,7 +149,7 @@ void GetAllMappingsRequest::doStart() { }); }; -SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& hash) : +SetMappingRequest::SetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) : _path(path.trimmed()), _hash(hash) { @@ -159,8 +159,8 @@ SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& has void SetMappingRequest::doStart() { // short circuit the request if the hash or path are invalid - auto validPath = isValidFilePath(_path); - auto validHash = isValidHash(_hash); + auto validPath = AssetUtils::isValidFilePath(_path); + auto validHash = AssetUtils::isValidHash(_hash); if (!validPath || !validHash) { _error = !validPath ? MappingRequest::InvalidPath : MappingRequest::InvalidHash; emit finished(this); @@ -170,17 +170,17 @@ void SetMappingRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->setAssetMapping(_path, _hash, - [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer message) { + [this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer message) { _mappingRequestID = INVALID_MESSAGE_ID; if (!responseReceived) { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; - case AssetServerError::PermissionDenied: + case AssetUtils::AssetServerError::PermissionDenied: _error = PermissionDenied; break; default: @@ -193,7 +193,7 @@ void SetMappingRequest::doStart() { }); }; -DeleteMappingsRequest::DeleteMappingsRequest(const AssetPathList& paths) : _paths(paths) { +DeleteMappingsRequest::DeleteMappingsRequest(const AssetUtils::AssetPathList& paths) : _paths(paths) { for (auto& path : _paths) { path = path.trimmed(); } @@ -203,7 +203,7 @@ void DeleteMappingsRequest::doStart() { // short circuit the request if any of the paths are invalid for (auto& path : _paths) { - if (!isValidPath(path)) { + if (!AssetUtils::isValidPath(path)) { _error = MappingRequest::InvalidPath; emit finished(this); return; @@ -213,17 +213,17 @@ void DeleteMappingsRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->deleteAssetMappings(_paths, - [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer message) { + [this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer message) { _mappingRequestID = INVALID_MESSAGE_ID; if (!responseReceived) { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; - case AssetServerError::PermissionDenied: + case AssetUtils::AssetServerError::PermissionDenied: _error = PermissionDenied; break; default: @@ -236,7 +236,7 @@ void DeleteMappingsRequest::doStart() { }); }; -RenameMappingRequest::RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) : +RenameMappingRequest::RenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath) : _oldPath(oldPath.trimmed()), _newPath(newPath.trimmed()) { @@ -246,7 +246,7 @@ RenameMappingRequest::RenameMappingRequest(const AssetPath& oldPath, const Asset void RenameMappingRequest::doStart() { // short circuit the request if either of the paths are invalid - if (!isValidFilePath(_oldPath) || !isValidFilePath(_newPath)) { + if (!AssetUtils::isValidFilePath(_oldPath) || !AssetUtils::isValidFilePath(_newPath)) { _error = InvalidPath; emit finished(this); return; @@ -255,17 +255,17 @@ void RenameMappingRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->renameAssetMapping(_oldPath, _newPath, - [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer message) { + [this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer message) { _mappingRequestID = INVALID_MESSAGE_ID; if (!responseReceived) { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; - case AssetServerError::PermissionDenied: + case AssetUtils::AssetServerError::PermissionDenied: _error = PermissionDenied; break; default: @@ -278,7 +278,7 @@ void RenameMappingRequest::doStart() { }); } -SetBakingEnabledRequest::SetBakingEnabledRequest(const AssetPathList& paths, bool enabled) : _paths(paths), _enabled(enabled) { +SetBakingEnabledRequest::SetBakingEnabledRequest(const AssetUtils::AssetPathList& paths, bool enabled) : _paths(paths), _enabled(enabled) { for (auto& path : _paths) { path = path.trimmed(); } @@ -288,7 +288,7 @@ void SetBakingEnabledRequest::doStart() { // short circuit the request if any of the paths are invalid for (auto& path : _paths) { - if (!isValidPath(path)) { + if (!AssetUtils::isValidPath(path)) { _error = MappingRequest::InvalidPath; emit finished(this); return; @@ -298,17 +298,17 @@ void SetBakingEnabledRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->setBakingEnabled(_paths, _enabled, - [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer message) { + [this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer message) { _mappingRequestID = INVALID_MESSAGE_ID; if (!responseReceived) { _error = NetworkError; } else { switch (error) { - case AssetServerError::NoError: + case AssetUtils::AssetServerError::NoError: _error = NoError; break; - case AssetServerError::PermissionDenied: + case AssetUtils::AssetServerError::PermissionDenied: _error = PermissionDenied; break; default: @@ -319,4 +319,4 @@ void SetBakingEnabledRequest::doStart() { emit finished(this); }); -}; \ No newline at end of file +}; diff --git a/libraries/networking/src/MappingRequest.h b/libraries/networking/src/MappingRequest.h index fc43375469..5286849dd1 100644 --- a/libraries/networking/src/MappingRequest.h +++ b/libraries/networking/src/MappingRequest.h @@ -50,10 +50,10 @@ private: class GetMappingRequest : public MappingRequest { Q_OBJECT public: - GetMappingRequest(const AssetPath& path); + GetMappingRequest(const AssetUtils::AssetPath& path); - AssetHash getHash() const { return _hash; } - AssetPath getRedirectedPath() const { return _redirectedPath; } + AssetUtils::AssetHash getHash() const { return _hash; } + AssetUtils::AssetPath getRedirectedPath() const { return _redirectedPath; } bool wasRedirected() const { return _wasRedirected; } signals: @@ -62,21 +62,21 @@ signals: private: virtual void doStart() override; - AssetPath _path; - AssetHash _hash; + AssetUtils::AssetPath _path; + AssetUtils::AssetHash _hash; - AssetPath _redirectedPath; + AssetUtils::AssetPath _redirectedPath; bool _wasRedirected { false }; }; class SetMappingRequest : public MappingRequest { Q_OBJECT public: - SetMappingRequest(const AssetPath& path, const AssetHash& hash); + SetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash); - AssetPath getPath() const { return _path; } - AssetHash getHash() const { return _hash; } + AssetUtils::AssetPath getPath() const { return _path; } + AssetUtils::AssetHash getHash() const { return _hash; } signals: void finished(SetMappingRequest* thisRequest); @@ -84,14 +84,14 @@ signals: private: virtual void doStart() override; - AssetPath _path; - AssetHash _hash; + AssetUtils::AssetPath _path; + AssetUtils::AssetHash _hash; }; class DeleteMappingsRequest : public MappingRequest { Q_OBJECT public: - DeleteMappingsRequest(const AssetPathList& path); + DeleteMappingsRequest(const AssetUtils::AssetPathList& path); signals: void finished(DeleteMappingsRequest* thisRequest); @@ -99,13 +99,13 @@ signals: private: virtual void doStart() override; - AssetPathList _paths; + AssetUtils::AssetPathList _paths; }; class RenameMappingRequest : public MappingRequest { Q_OBJECT public: - RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath); + RenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath); signals: void finished(RenameMappingRequest* thisRequest); @@ -113,8 +113,8 @@ signals: private: virtual void doStart() override; - AssetPath _oldPath; - AssetPath _newPath; + AssetUtils::AssetPath _oldPath; + AssetUtils::AssetPath _newPath; }; class GetAllMappingsRequest : public MappingRequest { @@ -122,7 +122,7 @@ class GetAllMappingsRequest : public MappingRequest { public: GetAllMappingsRequest(); - AssetMapping getMappings() const { return _mappings; } + AssetUtils::AssetMapping getMappings() const { return _mappings; } signals: void finished(GetAllMappingsRequest* thisRequest); @@ -130,13 +130,13 @@ signals: private: virtual void doStart() override; - AssetMapping _mappings; + AssetUtils::AssetMapping _mappings; }; class SetBakingEnabledRequest : public MappingRequest { Q_OBJECT public: - SetBakingEnabledRequest(const AssetPathList& path, bool enabled); + SetBakingEnabledRequest(const AssetUtils::AssetPathList& path, bool enabled); signals: void finished(SetBakingEnabledRequest* thisRequest); @@ -144,7 +144,7 @@ signals: private: virtual void doStart() override; - AssetPathList _paths; + AssetUtils::AssetPathList _paths; bool _enabled; }; From 31cdd5cca248f2b744a83d7d076406a190f46e20 Mon Sep 17 00:00:00 2001 From: humbletim Date: Thu, 18 Jan 2018 22:20:28 -0500 Subject: [PATCH 31/73] QueuedConnection per CR feedback --- libraries/shared/src/shared/MiniPromises.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h index 3385118666..2f17760aa8 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -54,7 +54,7 @@ public: Q_INVOKABLE void executeOnPromiseThread(std::function function) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( - this, "executeOnPromiseThread", Qt::BlockingQueuedConnection, + this, "executeOnPromiseThread", Qt::QueuedConnection, Q_ARG(std::function, function)); } else { function(); From 139266fe80c1662d40190c1c2a9286b853721858 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 19 Jan 2018 11:24:06 -0800 Subject: [PATCH 32/73] remove unneeded code --- interface/src/AvatarEntitiesBookmarks.cpp | 17 ----------------- interface/src/AvatarEntitiesBookmarks.h | 1 - 2 files changed, 18 deletions(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index 11d0d88368..437db595d3 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -93,23 +93,6 @@ void addAvatarEntities(const QVariantList& avatarEntities) { AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() { _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME; - readFromFile(); -} - -void AvatarEntitiesBookmarks::readFromFile() { - QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME; - QFile oldConfig(oldConfigPath); - // I imagine that in a year from now, this code for migrating (as well as the two lines above) - // may be removed since all bookmarks should have been migrated by then - // - Robbie Uvanni (6.8.2017) - if (oldConfig.exists()) { - if (QDir().rename(oldConfigPath, _bookmarksFilename)) { - qCDebug(interfaceapp) << "Successfully migrated" << AVATAR_ENTITIES_BOOKMARKS_FILENAME; - } else { - qCDebug(interfaceapp) << "Failed to migrate" << AVATAR_ENTITIES_BOOKMARKS_FILENAME; - } - } - Bookmarks::readFromFile(); } diff --git a/interface/src/AvatarEntitiesBookmarks.h b/interface/src/AvatarEntitiesBookmarks.h index 12e8c47768..310ec09f70 100644 --- a/interface/src/AvatarEntitiesBookmarks.h +++ b/interface/src/AvatarEntitiesBookmarks.h @@ -28,7 +28,6 @@ public slots: protected: void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override; - void readFromFile() override; private: const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json"; From c71fa110dbda9a69ba225f8f29e88ecdefcd4525 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 19 Jan 2018 11:31:13 -0800 Subject: [PATCH 33/73] update file dates and remove debug state --- interface/src/AvatarEntitiesBookmarks.cpp | 4 ++-- interface/src/AvatarEntitiesBookmarks.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index 437db595d3..2a70d1a42f 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -2,8 +2,8 @@ // AvatarEntitiesBookmarks.cpp // interface/src // -// Created by Dante Ruiz on /01/18. -// Copyright 2017 High Fidelity, Inc. +// Created by Dante Ruiz on 15/01/18. +// Copyright 2018 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 diff --git a/interface/src/AvatarEntitiesBookmarks.h b/interface/src/AvatarEntitiesBookmarks.h index 310ec09f70..0c70e4dbc0 100644 --- a/interface/src/AvatarEntitiesBookmarks.h +++ b/interface/src/AvatarEntitiesBookmarks.h @@ -3,7 +3,7 @@ // interface/src // // Created by Dante Ruiz on 15/01/18. -// Copyright 2017 High Fidelity, Inc. +// Copyright 2018 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 From 395cc663ddb9c082f22d1cb6ff7bab45f43b189c Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 19 Jan 2018 15:10:36 -0500 Subject: [PATCH 34/73] reuse same disk cache as AssetClient --- libraries/networking/src/AssetClient.cpp | 99 +++++++++++++++++++ libraries/networking/src/AssetClient.h | 3 + .../src/BaseAssetScriptingInterface.cpp | 99 ++++--------------- .../src/BaseAssetScriptingInterface.h | 20 ++-- .../src/AssetScriptingInterface.cpp | 22 ----- 5 files changed, 128 insertions(+), 115 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 6980621219..c126fc2e5a 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -100,6 +100,105 @@ void AssetClient::cacheInfoRequest(MiniPromise::Promise deferred) { } } +void AssetClient::queryCacheMeta(MiniPromise::Promise deferred, const QUrl& url) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "cacheInfoRequest", Q_ARG(MiniPromise::Promise, deferred), Q_ARG(const QUrl&, url)); + return; + } + if (auto cache = NetworkAccessManager::getInstance().cache()) { + QNetworkCacheMetaData metaData = cache->metaData(url); + QVariantMap attributes, rawHeaders; + + QHashIterator i(metaData.attributes()); + while (i.hasNext()) { + i.next(); + attributes[QString::number(i.key())] = i.value(); + } + for (const auto& i : metaData.rawHeaders()) { + rawHeaders[i.first] = i.second; + } + deferred->resolve({ + { "isValid", metaData.isValid() }, + { "url", metaData.url() }, + { "expirationDate", metaData.expirationDate() }, + { "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() }, + { "saveToDisk", metaData.saveToDisk() }, + { "attributes", attributes }, + { "rawHeaders", rawHeaders }, + }); + } else { + deferred->reject("cache currently unavailable"); + } +} + +void AssetClient::loadFromCache(MiniPromise::Promise deferred, const QUrl& url) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadFromCache", Q_ARG(MiniPromise::Promise, deferred), Q_ARG(const QUrl&, url)); + return; + } + if (auto cache = NetworkAccessManager::getInstance().cache()) { + MiniPromise::Promise metaRequest = makePromise(__FUNCTION__); + queryCacheMeta(metaRequest, url); + metaRequest->then([&](QString error, QVariantMap metadata) { + if (!error.isEmpty()) { + deferred->reject(error, metadata); + return; + } + QVariantMap result = { + { "metadata", metadata }, + { "data", QByteArray() }, + }; + // caller is responsible for the deletion of the ioDevice, hence the unique_ptr + if (auto ioDevice = std::unique_ptr(cache->data(url))) { + QByteArray data = ioDevice->readAll(); + result["data"] = data; + } else { + error = "cache data unavailable"; + } + deferred->handle(error, result); + }); + } else { + deferred->reject("cache currently unavailable"); + } +} + +namespace { + // parse RFC 1123 HTTP date format + QDateTime parseHttpDate(const QString& dateString) { + QDateTime dt = QDateTime::fromString(dateString.left(25), "ddd, dd MMM yyyy HH:mm:ss"); + dt.setTimeSpec(Qt::UTC); + return dt; + } +} + +void AssetClient::saveToCache(MiniPromise::Promise deferred, const QUrl& url, const QByteArray& data, const QVariantMap& headers) { + if (auto cache = NetworkAccessManager::getInstance().cache()) { + QDateTime lastModified = headers.contains("last-modified") ? + parseHttpDate(headers["last-modified"].toString()) : + QDateTime::currentDateTimeUtc(); + QDateTime expirationDate = headers.contains("expires") ? + parseHttpDate(headers["expires"].toString()) : + QDateTime(); // never expires + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + metaData.setSaveToDisk(true); + metaData.setLastModified(lastModified); + metaData.setExpirationDate(expirationDate); + if (auto ioDevice = cache->prepare(metaData)) { + ioDevice->write(data); + cache->insert(ioDevice); + qCDebug(asset_client) << url.toDisplayString() << "saved to disk cache ("<< data.size()<<" bytes)"; + deferred->resolve({{ "success", true }}); + } else { + auto error = QString("Could not save %1 to disk cache").arg(url.toDisplayString()); + qCWarning(asset_client) << error; + deferred->reject(error); + } + } else { + deferred->reject("cache currently unavailable"); + } +} + void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection, diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index c10ecf78a3..81149bf3d6 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -68,6 +68,9 @@ public slots: void cacheInfoRequest(QObject* reciever, QString slot); void cacheInfoRequest(MiniPromise::Promise deferred); + void queryCacheMeta(MiniPromise::Promise deferred, const QUrl& url); + void loadFromCache(MiniPromise::Promise deferred, const QUrl& url); + void saveToCache(MiniPromise::Promise deferred, const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); void clearCache(); private slots: diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index 8172f12c79..f6f7fd87e3 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -31,31 +31,23 @@ QSharedPointer BaseAssetScriptingInterface::assetClient() { return DependencyManager::get(); } -BaseAssetScriptingInterface::BaseAssetScriptingInterface(QObject* parent) : QObject(parent), _cache(this) { -} - +BaseAssetScriptingInterface::BaseAssetScriptingInterface(QObject* parent) : QObject(parent) {} bool BaseAssetScriptingInterface::initializeCache() { - // NOTE: *instances* of QNetworkDiskCache are not thread-safe -- however, different threads can effectively - // use the same underlying cache if configured with identical settings. Once AssetClient's disk cache settings - // become available we configure our instance to match. auto assets = assetClient(); if (!assets) { return false; // not yet possible to initialize the cache } - if (_cache.cacheDirectory().size()) { + if (!_cacheDirectory.isEmpty()) { return true; // cache is ready } // attempt to initialize the cache - QMetaObject::invokeMethod(assetClient().data(), "init"); + QMetaObject::invokeMethod(assets.data(), "init"); Promise deferred = makePromise("BaseAssetScriptingInterface--queryCacheStatus"); deferred->then([&](QVariantMap result) { - auto cacheDirectory = result.value("cacheDirectory").toString(); - auto maximumCacheSize = result.value("maximumCacheSize").toLongLong(); - _cache.setCacheDirectory(cacheDirectory); - _cache.setMaximumCacheSize(maximumCacheSize); + _cacheDirectory = result.value("cacheDirectory").toString(); }); deferred->fail([&](QString error) { qDebug() << "BaseAssetScriptingInterface::queryCacheStatus ERROR" << QThread::currentThread() << error; @@ -64,79 +56,28 @@ bool BaseAssetScriptingInterface::initializeCache() { return false; // cache is not ready yet } -QVariantMap BaseAssetScriptingInterface::getCacheStatus() { - return { - { "cacheDirectory", _cache.cacheDirectory() }, - { "cacheSize", _cache.cacheSize() }, - { "maximumCacheSize", _cache.maximumCacheSize() }, - }; +Promise BaseAssetScriptingInterface::getCacheStatus() { + Promise deferred = makePromise(__FUNCTION__); + DependencyManager::get()->cacheInfoRequest(deferred); + return deferred; } -QVariantMap BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) { - QNetworkCacheMetaData metaData = _cache.metaData(url); - QVariantMap attributes, rawHeaders; - - QHashIterator i(metaData.attributes()); - while (i.hasNext()) { - i.next(); - attributes[QString::number(i.key())] = i.value(); - } - for (const auto& i : metaData.rawHeaders()) { - rawHeaders[i.first] = i.second; - } - return { - { "isValid", metaData.isValid() }, - { "url", metaData.url() }, - { "expirationDate", metaData.expirationDate() }, - { "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() }, - { "saveToDisk", metaData.saveToDisk() }, - { "attributes", attributes }, - { "rawHeaders", rawHeaders }, - }; +Promise BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) { + Promise deferred = makePromise(__FUNCTION__); + DependencyManager::get()->queryCacheMeta(deferred, url); + return deferred; } -QVariantMap BaseAssetScriptingInterface::loadFromCache(const QUrl& url) { - QVariantMap result = { - { "metadata", queryCacheMeta(url) }, - { "data", QByteArray() }, - }; - // caller is responsible for the deletion of the ioDevice, hence the unique_ptr - if (auto ioDevice = std::unique_ptr(_cache.data(url))) { - QByteArray data = ioDevice->readAll(); - result["data"] = data; - } - return result; +Promise BaseAssetScriptingInterface::loadFromCache(const QUrl& url) { + Promise deferred = makePromise(__FUNCTION__); + DependencyManager::get()->loadFromCache(deferred, url); + return deferred; } -namespace { - // parse RFC 1123 HTTP date format - QDateTime parseHttpDate(const QString& dateString) { - QDateTime dt = QDateTime::fromString(dateString.left(25), "ddd, dd MMM yyyy HH:mm:ss"); - dt.setTimeSpec(Qt::UTC); - return dt; - } -} - -bool BaseAssetScriptingInterface::saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& headers) { - QDateTime lastModified = headers.contains("last-modified") ? - parseHttpDate(headers["last-modified"].toString()) : - QDateTime::currentDateTimeUtc(); - QDateTime expirationDate = headers.contains("expires") ? - parseHttpDate(headers["expires"].toString()) : - QDateTime(); // never expires - QNetworkCacheMetaData metaData; - metaData.setUrl(url); - metaData.setSaveToDisk(true); - metaData.setLastModified(lastModified); - metaData.setExpirationDate(expirationDate); - if (auto ioDevice = _cache.prepare(metaData)) { - ioDevice->write(data); - _cache.insert(ioDevice); - qCDebug(asset_client) << url.toDisplayString() << "saved to disk cache ("<< data.size()<<" bytes)"; - return true; - } - qCWarning(asset_client) << "Could not save" << url.toDisplayString() << "to disk cache."; - return false; +Promise BaseAssetScriptingInterface::saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& headers) { + Promise deferred = makePromise(__FUNCTION__); + DependencyManager::get()->saveToCache(deferred, url, data, headers); + return deferred; } Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, QString responseType) { diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index 3aca3d394e..35c829fd37 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -33,15 +33,7 @@ public: BaseAssetScriptingInterface(QObject* parent = nullptr); public slots: - /**jsdoc - * Get the current status of the disk cache (if available) - * @function Assets.uploadData - * @static - * @return {String} result.cacheDirectory (path to the current disk cache) - * @return {Number} result.cacheSize (used cache size in bytes) - * @return {Number} result.maximumCacheSize (maxmimum cache size in bytes) - */ - QVariantMap getCacheStatus(); + Promise getCacheStatus(); /**jsdoc * Initialize the disk cache (returns true if already initialized) @@ -56,14 +48,14 @@ public slots: QString extractAssetHash(QString input) { return AssetUtils::extractAssetHash(input); } bool isValidHash(QString input) { return AssetUtils::isValidHash(input); } QByteArray hashData(const QByteArray& data) { return AssetUtils::hashData(data); } + QString hashDataHex(const QByteArray& data) { return hashData(data).toHex(); } - virtual QVariantMap queryCacheMeta(const QUrl& url); - virtual QVariantMap loadFromCache(const QUrl& url); - virtual bool saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); + virtual Promise queryCacheMeta(const QUrl& url); + virtual Promise loadFromCache(const QUrl& url); + virtual Promise saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); protected: - //void onCacheInfoResponse(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize); - QNetworkDiskCache _cache; + QString _cacheDirectory; const QString NoError{}; //virtual bool jsAssert(bool condition, const QString& error) = 0; Promise loadAsset(QString asset, bool decompress, QString responseType); diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index d80491b2a6..0870460a41 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -75,28 +75,6 @@ void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValu setMappingRequest->start(); } -void AssetScriptingInterface::getMapping(QString path, QScriptValue callback) { - auto request = DependencyManager::get()->createGetMappingRequest(path); - QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { - auto result = request->getError(); - if (callback.isFunction()) { - if (result == GetMappingRequest::NotFound) { - QScriptValueList args { "", true }; - callback.call(_engine->currentContext()->thisObject(), args); - } else if (result == GetMappingRequest::NoError) { - QScriptValueList args { request->getHash(), true }; - callback.call(_engine->currentContext()->thisObject(), args); - } else { - qCDebug(scriptengine) << "error -- " << request->getError() << " -- " << request->getErrorString(); - QScriptValueList args { "", false }; - callback.call(_engine->currentContext()->thisObject(), args); - } - request->deleteLater(); - } - }); - request->start(); -} - void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) { // FIXME: historically this API method failed silently when given a non-atp prefixed // urlString (or if the AssetRequest failed). From a421ba7649d4ba9853ad81d21b88e26645a1f1ba Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 19 Jan 2018 14:50:51 -0800 Subject: [PATCH 35/73] Add "pitch" parameter to scriptable audio injector options. Used in playSound() to pitch-shift the audio when injector is created. --- libraries/audio/src/AudioInjector.cpp | 37 +++++++++++++++++--- libraries/audio/src/AudioInjectorOptions.cpp | 10 +++++- libraries/audio/src/AudioInjectorOptions.h | 1 + 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 9f32372a8e..d4ca6093f0 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -463,10 +463,39 @@ AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) { - AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options); - if (!injector->inject(&AudioInjectorManager::threadInjector)) { - qWarning() << "AudioInjector::playSound failed to thread injector"; + if (options.pitch == 1.0f) { + + AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options); + + if (!injector->inject(&AudioInjectorManager::threadInjector)) { + qWarning() << "AudioInjector::playSound failed to thread injector"; + } + return injector; + + } else { + + const int standardRate = AudioConstants::SAMPLE_RATE; + const int resampledRate = AudioConstants::SAMPLE_RATE / glm::clamp(options.pitch, 1/16.0f, 16.0f); // limit to 4 octaves + const int numChannels = options.ambisonic ? AudioConstants::AMBISONIC : + (options.stereo ? AudioConstants::STEREO : AudioConstants::MONO); + + AudioSRC resampler(standardRate, resampledRate, numChannels); + + // create a resampled buffer that is guaranteed to be large enough + const int nInputFrames = buffer.size() / (numChannels * sizeof(int16_t)); + const int maxOutputFrames = resampler.getMaxOutput(nInputFrames); + QByteArray resampledBuffer(maxOutputFrames * numChannels * sizeof(int16_t), '\0'); + + resampler.render(reinterpret_cast(buffer.data()), + reinterpret_cast(resampledBuffer.data()), + nInputFrames); + + AudioInjectorPointer injector = AudioInjectorPointer::create(resampledBuffer, options); + + if (!injector->inject(&AudioInjectorManager::threadInjector)) { + qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector"; + } + return injector; } - return injector; } diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 0af74a796c..2f82cae137 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -26,7 +26,8 @@ AudioInjectorOptions::AudioInjectorOptions() : ambisonic(false), ignorePenumbra(false), localOnly(false), - secondOffset(0.0f) + secondOffset(0.0f), + pitch(1.0f) { } @@ -40,6 +41,7 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje obj.setProperty("ignorePenumbra", injectorOptions.ignorePenumbra); obj.setProperty("localOnly", injectorOptions.localOnly); obj.setProperty("secondOffset", injectorOptions.secondOffset); + obj.setProperty("pitch", injectorOptions.pitch); return obj; } @@ -87,6 +89,12 @@ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOpt } else { qCWarning(audio) << "Audio injector options: secondOffset is not a number"; } + } else if (it.name() == "pitch") { + if (it.value().isNumber()) { + injectorOptions.pitch = it.value().toNumber(); + } else { + qCWarning(audio) << "Audio injector options: pitch is not a number"; + } } else { qCWarning(audio) << "Unknown audio injector option:" << it.name(); } diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index 40a3f343bd..4dd38ce915 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -29,6 +29,7 @@ public: bool ignorePenumbra; bool localOnly; float secondOffset; + float pitch; // multiplier, where 2.0f shifts up one octave }; Q_DECLARE_METATYPE(AudioInjectorOptions); From e2e4e7f423a12cebf94cc59bfb2c7b5715b3d0ea Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 19 Jan 2018 16:03:59 -0800 Subject: [PATCH 36/73] Re-implement collision sound pitch shifting --- libraries/audio/src/AudioInjector.cpp | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index d4ca6093f0..d1b919292a 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -427,28 +427,11 @@ AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const fl options.stereo = sound->isStereo(); options.position = position; options.volume = volume; + options.pitch = 1.0f / stretchFactor; QByteArray samples = sound->getByteArray(); - if (stretchFactor == 1.0f) { - return playSoundAndDelete(samples, options); - } - const int standardRate = AudioConstants::SAMPLE_RATE; - const int resampledRate = standardRate * stretchFactor; - const int channelCount = sound->isStereo() ? 2 : 1; - - AudioSRC resampler(standardRate, resampledRate, channelCount); - - const int nInputFrames = samples.size() / (channelCount * sizeof(int16_t)); - const int maxOutputFrames = resampler.getMaxOutput(nInputFrames); - QByteArray resampled(maxOutputFrames * channelCount * sizeof(int16_t), '\0'); - - int nOutputFrames = resampler.render(reinterpret_cast(samples.data()), - reinterpret_cast(resampled.data()), - nInputFrames); - - Q_UNUSED(nOutputFrames); - return playSoundAndDelete(resampled, options); + return playSoundAndDelete(samples, options); } AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) { @@ -461,7 +444,6 @@ AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, return sound; } - AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) { if (options.pitch == 1.0f) { From 027a1e69f478693ddec6763a41f05c4300b691c4 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 20 Jan 2018 00:08:06 -0800 Subject: [PATCH 37/73] some settings cleanup, adding description to Send Data --- interface/src/ui/PreferencesDialog.cpp | 40 ++++++++----------- .../src/display-plugins/CompositorHelper.h | 3 -- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 1a09af07ab..1bd6ef5889 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -142,7 +142,10 @@ void setupPreferences() { { auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; - preferences->addPreference(new CheckPreference("Privacy", "Send data", getter, setter)); + preferences->addPreference(new CheckPreference("Privacy", "Send data - High Fidelity uses information provided by your " + "client to improve the product through the logging of errors and tracking of usage patterns, " + "installation and system details, and crash events. By allowing High Fidelity to collect " + "this information you are helping to improve the product. ", getter, setter)); } static const QString LOD_TUNING("Level of Detail Tuning"); @@ -297,26 +300,6 @@ void setupPreferences() { } #endif - { - auto getter = []()->float { return qApp->getMaxOctreePacketsPerSecond(); }; - auto setter = [](float value) { qApp->setMaxOctreePacketsPerSecond(value); }; - auto preference = new SpinnerPreference("Octree", "Max packets sent each second", getter, setter); - preference->setMin(60); - preference->setMax(6000); - preference->setStep(10); - preferences->addPreference(preference); - } - - - { - auto getter = []()->float { return qApp->getApplicationCompositor().getHmdUIAngularSize(); }; - auto setter = [](float value) { qApp->getApplicationCompositor().setHmdUIAngularSize(value); }; - auto preference = new SpinnerPreference("HMD", "UI horizontal angular size (degrees)", getter, setter); - preference->setMin(30); - preference->setMax(160); - preference->setStep(1); - preferences->addPreference(preference); - } { static const QString RENDER("Graphics"); @@ -342,7 +325,7 @@ void setupPreferences() { } } { - static const QString RENDER("Networking"); + static const QString NETWORKING("Networking"); auto nodelist = DependencyManager::get(); { @@ -350,10 +333,21 @@ void setupPreferences() { static const int MAX_PORT_NUMBER { 65535 }; auto getter = [nodelist] { return static_cast(nodelist->getSocketLocalPort()); }; auto setter = [nodelist](int preset) { nodelist->setSocketLocalPort(static_cast(preset)); }; - auto preference = new IntSpinnerPreference(RENDER, "Listening Port", getter, setter); + auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter); preference->setMin(MIN_PORT_NUMBER); preference->setMax(MAX_PORT_NUMBER); preferences->addPreference(preference); } + + { + auto getter = []()->float { return qApp->getMaxOctreePacketsPerSecond(); }; + auto setter = [](float value) { qApp->setMaxOctreePacketsPerSecond(value); }; + auto preference = new SpinnerPreference(NETWORKING, "Max entities packets sent each second", getter, setter); + preference->setMin(60); + preference->setMax(6000); + preference->setStep(10); + preferences->addPreference(preference); + } + } } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index f448375f0d..6c2acb7e62 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -53,8 +53,6 @@ public: bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; - float getHmdUIAngularSize() const { return _hmdUIAngularSize; } - void setHmdUIAngularSize(float hmdUIAngularSize) { _hmdUIAngularSize = hmdUIAngularSize; } bool isHMD() const; bool fakeEventActive() const { return _fakeMouseEvent; } @@ -139,7 +137,6 @@ private: //quint64 _hoverItemEnterUsecs { 0 }; bool _isOverDesktop { true }; - float _hmdUIAngularSize { glm::degrees(VIRTUAL_UI_TARGET_FOV.y) }; float _textureFov { VIRTUAL_UI_TARGET_FOV.y }; float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; From ab9041bcab20c61468b83bd51202bba98df39c7f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 20 Jan 2018 00:14:44 -0800 Subject: [PATCH 38/73] remove LOAD DEFAULT SCRIPTS from General Preferences, since it already exists on Running Scripts --- interface/src/ui/PreferencesDialog.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 1bd6ef5889..1d4d6f94b2 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -135,10 +135,6 @@ void setupPreferences() { preferences->addPreference(new BrowsePreference("Scripts", "Load scripts from this directory", getter, setter)); } - preferences->addPreference(new ButtonPreference("Scripts", "Load Default Scripts", [] { - DependencyManager::get()->loadDefaultScripts(); - })); - { auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; From 4ad5d34a1078aabaf04743d607ebc1b0698117fb Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 20 Jan 2018 17:20:42 -0800 Subject: [PATCH 39/73] more preferences cleanup, removing some dead code from ScriptEngines/ScriptModel --- .../dialogs/AdvancedPreferencesDialog.qml | 29 +++++++++ .../hifi/dialogs/GeneralPreferencesDialog.qml | 2 +- interface/src/Application.cpp | 5 +- interface/src/Menu.cpp | 7 ++ interface/src/ui/PreferencesDialog.cpp | 65 +++++++++---------- libraries/script-engine/src/ScriptEngines.cpp | 12 +--- libraries/script-engine/src/ScriptEngines.h | 5 +- libraries/script-engine/src/ScriptsModel.cpp | 3 +- 8 files changed, 79 insertions(+), 49 deletions(-) create mode 100644 interface/resources/qml/hifi/dialogs/AdvancedPreferencesDialog.qml diff --git a/interface/resources/qml/hifi/dialogs/AdvancedPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/AdvancedPreferencesDialog.qml new file mode 100644 index 0000000000..2d80a1c330 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/AdvancedPreferencesDialog.qml @@ -0,0 +1,29 @@ +// +// AdvancedPreferencesDialog.qml +// +// Created by Brad Hefta-Gaub on 20 Jan 2018 +// Copyright 2018 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 +// + +import QtQuick 2.5 +import Qt.labs.settings 1.0 + +import "../../dialogs" + +PreferencesDialog { + id: root + objectName: "AdvancedPreferencesDialog" + title: "Advanced Settings" + showCategories: ["Advanced UI" ] + property var settings: Settings { + category: root.objectName + property alias x: root.x + property alias y: root.y + property alias width: root.width + property alias height: root.height + } +} + diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml index 94665794d6..6f5798e2b2 100644 --- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml @@ -17,7 +17,7 @@ PreferencesDialog { id: root objectName: "GeneralPreferencesDialog" title: "General Settings" - showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Game Controller", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"] + showCategories: ["UI", "Snapshots", "Privacy", "HMD", "Game Controller", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f3c41565f8..cf5b83aafd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1375,8 +1375,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo userInputMapper->registerDevice(_touchscreenDevice->getInputDevice()); } - // force the model the look at the correct directory (weird order of operations issue) - scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation()); + // this will force the model the look at the correct directory (weird order of operations issue) + scriptEngines->reloadLocalFiles(); + // do this as late as possible so that all required subsystems are initialized // If we've overridden the default scripts location, just load default scripts // otherwise, load 'em all diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d295e96867..50142d6a3c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -758,6 +758,13 @@ Menu::Menu() { // Developer > Stats addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + // Developer > Advanced Settings... + action = addActionToQMenuAndActionHash(developerMenu, "Advanced Preferences..."); + connect(action, &QAction::triggered, [] { + qApp->showDialog(QString("hifi/dialogs/AdvancedPreferencesDialog.qml"), + QString("hifi/tablet/AdvancedPreferencesDialog.qml"), "AdvancedPreferencesDialog"); + }); + #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 1d4d6f94b2..48b56c7ced 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -82,19 +82,42 @@ void setupPreferences() { preference->setMax(500); preferences->addPreference(preference); } - { - auto getter = []()->float { return qApp->getDesktopTabletScale(); }; - auto setter = [](float value) { qApp->setDesktopTabletScale(value); }; - auto preference = new SpinnerPreference(UI_CATEGORY, "Desktop Tablet Scale %", getter, setter); - preference->setMin(20); - preference->setMax(500); - preferences->addPreference(preference); - } + + { auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); }; auto setter = [](bool value) { qApp->setPreferStylusOverLaser(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter)); } + + static const QString ADVANCED_UI_CATEGORY { "Advanced UI" }; + { + auto getter = []()->float { return qApp->getDesktopTabletScale(); }; + auto setter = [](float value) { qApp->setDesktopTabletScale(value); }; + auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Desktop Tablet Scale %", getter, setter); + preference->setMin(20); + preference->setMax(500); + preferences->addPreference(preference); + } + { + auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; + auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); }; + auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Real world vertical field of view (angular size of monitor)", getter, setter); + preference->setMin(1); + preference->setMax(180); + preferences->addPreference(preference); + } + { + auto getter = []()->float { return qApp->getFieldOfView(); }; + auto setter = [](float value) { qApp->setFieldOfView(value); }; + auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Vertical field of view", getter, setter); + preference->setMin(1); + preference->setMax(180); + preference->setStep(1); + preferences->addPreference(preference); + } + + // FIXME: Remove setting completely or make available through JavaScript API? /* { @@ -128,18 +151,11 @@ void setupPreferences() { preferences->addPreference(preference); } - // Scripts - { - auto getter = []()->QString { return DependencyManager::get()->getScriptsLocation(); }; - auto setter = [](const QString& value) { DependencyManager::get()->setScriptsLocation(value); }; - preferences->addPreference(new BrowsePreference("Scripts", "Load scripts from this directory", getter, setter)); - } - { auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); }; auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); }; preferences->addPreference(new CheckPreference("Privacy", "Send data - High Fidelity uses information provided by your " - "client to improve the product through the logging of errors and tracking of usage patterns, " + "client to improve the product through the logging of errors, tracking of usage patterns, " "installation and system details, and crash events. By allowing High Fidelity to collect " "this information you are helping to improve the product. ", getter, setter)); } @@ -166,23 +182,6 @@ void setupPreferences() { } static const QString AVATAR_TUNING { "Avatar Tuning" }; - { - auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; - auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); }; - auto preference = new SpinnerPreference(AVATAR_TUNING, "Real world vertical field of view (angular size of monitor)", getter, setter); - preference->setMin(1); - preference->setMax(180); - preferences->addPreference(preference); - } - { - auto getter = []()->float { return qApp->getFieldOfView(); }; - auto setter = [](float value) { qApp->setFieldOfView(value); }; - auto preference = new SpinnerPreference(AVATAR_TUNING, "Vertical field of view", getter, setter); - preference->setMin(1); - preference->setMax(180); - preference->setStep(1); - preferences->addPreference(preference); - } { auto getter = [=]()->QString { return myAvatar->getDominantHand(); }; auto setter = [=](const QString& value) { myAvatar->setDominantHand(value); }; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 6cfa678652..78cb05fa0d 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -62,8 +62,7 @@ void ScriptEngines::onErrorLoadingScript(const QString& url) { } ScriptEngines::ScriptEngines(ScriptEngine::Context context) - : _context(context), - _scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION) + : _context(context) { _scriptsModelFilter.setSourceModel(&_scriptsModel); _scriptsModelFilter.sort(0, Qt::AscendingOrder); @@ -429,13 +428,8 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { return stoppedScript; } -QString ScriptEngines::getScriptsLocation() const { - return _scriptsLocationHandle.get(); -} - -void ScriptEngines::setScriptsLocation(const QString& scriptsLocation) { - _scriptsLocationHandle.set(scriptsLocation); - _scriptsModel.updateScriptsLocation(scriptsLocation); +void ScriptEngines::reloadLocalFiles() { + _scriptsModel.reloadLocalFiles(); } void ScriptEngines::reloadAllScripts() { diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index f2b0105be1..265902143f 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -41,9 +41,9 @@ public: void loadScripts(); void saveScripts(); - QString getScriptsLocation() const; void loadDefaultScripts(); - void setScriptsLocation(const QString& scriptsLocation); + void reloadLocalFiles(); + QStringList getRunningScripts(); ScriptEnginePointer getScriptEngine(const QUrl& scriptHash); @@ -111,7 +111,6 @@ protected: QSet _allKnownScriptEngines; QMutex _allScriptsMutex; std::list _scriptInitializers; - mutable Setting::Handle _scriptsLocationHandle; ScriptsModel _scriptsModel; ScriptsModelFilter _scriptsModelFilter; std::atomic _isStopped { false }; diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 9b7a367773..5f30033bf9 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -100,6 +100,7 @@ QVariant ScriptsModel::data(const QModelIndex& index, int role) const { return QVariant(); } if (node->getType() == TREE_NODE_TYPE_SCRIPT) { + TreeNodeScript* script = static_cast(node); if (role == Qt::DisplayRole) { return QVariant(script->getName() + (script->getOrigin() == SCRIPT_ORIGIN_LOCAL ? " (local)" : "")); @@ -133,7 +134,6 @@ void ScriptsModel::updateScriptsLocation(const QString& newPath) { _fsWatcher.addPath(_localDirectory.absolutePath()); } } - reloadLocalFiles(); } @@ -305,6 +305,7 @@ void ScriptsModel::rebuildTree() { _treeNodes.removeAt(i); } } + QHash folders; for (int i = 0; i < _treeNodes.size(); i++) { TreeNodeBase* node = _treeNodes.at(i); From 43f7bb5f56e9dbd72939a54e2cc1f63821ba2fa8 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 22 Jan 2018 16:50:03 +0100 Subject: [PATCH 40/73] Fixed compilation error due to really long shader --- libraries/render-utils/src/LightClusterGrid.slh | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/libraries/render-utils/src/LightClusterGrid.slh b/libraries/render-utils/src/LightClusterGrid.slh index 60daa520a3..709e8c0f70 100644 --- a/libraries/render-utils/src/LightClusterGrid.slh +++ b/libraries/render-utils/src/LightClusterGrid.slh @@ -100,23 +100,6 @@ int clusterGrid_getClusterLightId(int index, int offset) { <@endfunc@> bool hasLocalLights(int numLights, ivec3 clusterPos, ivec3 dims) { -/* - if (numLights <= 0) { - return false; - } - - if (clusterPos.x < 0 || clusterPos.x >= dims.x) { - return false; - } - - if (clusterPos.y < 0 || clusterPos.y >= dims.y) { - return false; - } - if (clusterPos.z < 0 || clusterPos.z > dims.z) { - return false; - } - return true; -*/ return numLights>0 && all(greaterThanEqual(clusterPos, ivec3(0))) && all(lessThan(clusterPos.xy, dims.xy)) From 6d6950decafc9a80b83ec907d8065f1b9680a5c4 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 22 Jan 2018 16:25:58 -0700 Subject: [PATCH 41/73] Fix Interface first launch should show help --- interface/src/Application.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ffe2f781db..7534aec947 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -334,7 +334,7 @@ static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f; static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html"; -static const QString INFO_HELP_PATH = "../../../html/tabletHelp.html"; +static const QString INFO_HELP_PATH = "html/tabletHelp.html"; static const unsigned int THROTTLED_SIM_FRAMERATE = 15; static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE; @@ -2660,7 +2660,8 @@ void Application::showHelp() { queryString.addQueryItem("defaultTab", defaultTab); auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); - tablet->gotoWebScreen(INFO_HELP_PATH + "?" + queryString.toString()); + tablet->gotoWebScreen(PathUtils::resourcesPath() + INFO_HELP_PATH + "?" + queryString.toString()); + DependencyManager::get()->openTablet(); //InfoView::show(INFO_HELP_PATH, false, queryString.toString()); } From 3a735c1fc7d12809289fc225cc336bc0c4902060 Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 23 Jan 2018 03:12:26 -0500 Subject: [PATCH 42/73] CR feedback and code cleanup --- libraries/networking/src/AssetClient.cpp | 234 ++++++++------ libraries/networking/src/AssetClient.h | 8 +- libraries/networking/src/AssetUtils.cpp | 2 +- .../src/BaseAssetScriptingInterface.cpp | 187 ++++++----- .../src/BaseAssetScriptingInterface.h | 26 +- .../src/ArrayBufferViewClass.cpp | 35 +- .../script-engine/src/ArrayBufferViewClass.h | 4 + .../src/AssetScriptingInterface.cpp | 306 ++++++++++-------- .../src/AssetScriptingInterface.h | 91 +++++- libraries/script-engine/src/ScriptEngine.cpp | 4 +- libraries/script-engine/src/ScriptEngine.h | 2 +- libraries/shared/src/BaseScriptEngine.cpp | 6 + libraries/shared/src/shared/MiniPromises.cpp | 17 + libraries/shared/src/shared/MiniPromises.h | 107 +++--- 14 files changed, 658 insertions(+), 371 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index c126fc2e5a..a0c86a25e8 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -81,122 +81,170 @@ void AssetClient::init() { } -void AssetClient::cacheInfoRequest(MiniPromise::Promise deferred) { - if (QThread::currentThread() != thread()) { - if (!QMetaType::isRegistered(qMetaTypeId())) { - qRegisterMetaType(); - } - QMetaObject::invokeMethod(this, "cacheInfoRequest", Q_ARG(MiniPromise::Promise, deferred)); - return; - } - if (auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache())) { - deferred->resolve({ - { "cacheDirectory", cache->cacheDirectory() }, - { "cacheSize", cache->cacheSize() }, - { "maximumCacheSize", cache->maximumCacheSize() }, - }); - } else { - deferred->reject("Cache not available"); - } +namespace { + const QString& CACHE_ERROR_MESSAGE{ "AssetClient::Error: %1 %2" }; } -void AssetClient::queryCacheMeta(MiniPromise::Promise deferred, const QUrl& url) { +MiniPromise::Promise AssetClient::cacheInfoRequestAsync(MiniPromise::Promise deferred) { + if (!deferred) { + deferred = makePromise(__FUNCTION__); // create on caller's thread + } if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "cacheInfoRequest", Q_ARG(MiniPromise::Promise, deferred), Q_ARG(const QUrl&, url)); - return; - } - if (auto cache = NetworkAccessManager::getInstance().cache()) { - QNetworkCacheMetaData metaData = cache->metaData(url); - QVariantMap attributes, rawHeaders; - - QHashIterator i(metaData.attributes()); - while (i.hasNext()) { - i.next(); - attributes[QString::number(i.key())] = i.value(); - } - for (const auto& i : metaData.rawHeaders()) { - rawHeaders[i.first] = i.second; - } - deferred->resolve({ - { "isValid", metaData.isValid() }, - { "url", metaData.url() }, - { "expirationDate", metaData.expirationDate() }, - { "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() }, - { "saveToDisk", metaData.saveToDisk() }, - { "attributes", attributes }, - { "rawHeaders", rawHeaders }, - }); + QMetaObject::invokeMethod(this, "cacheInfoRequestAsync", Q_ARG(MiniPromise::Promise, deferred)); } else { - deferred->reject("cache currently unavailable"); + auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache()); + if (cache) { + deferred->resolve({ + { "cacheDirectory", cache->cacheDirectory() }, + { "cacheSize", cache->cacheSize() }, + { "maximumCacheSize", cache->maximumCacheSize() }, + }); + } else { + deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg("cache unavailable")); + } } + return deferred; } -void AssetClient::loadFromCache(MiniPromise::Promise deferred, const QUrl& url) { +MiniPromise::Promise AssetClient::queryCacheMetaAsync(const QUrl& url, MiniPromise::Promise deferred) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadFromCache", Q_ARG(MiniPromise::Promise, deferred), Q_ARG(const QUrl&, url)); - return; - } - if (auto cache = NetworkAccessManager::getInstance().cache()) { - MiniPromise::Promise metaRequest = makePromise(__FUNCTION__); - queryCacheMeta(metaRequest, url); - metaRequest->then([&](QString error, QVariantMap metadata) { - if (!error.isEmpty()) { - deferred->reject(error, metadata); - return; - } - QVariantMap result = { - { "metadata", metadata }, - { "data", QByteArray() }, - }; - // caller is responsible for the deletion of the ioDevice, hence the unique_ptr - if (auto ioDevice = std::unique_ptr(cache->data(url))) { - QByteArray data = ioDevice->readAll(); - result["data"] = data; + QMetaObject::invokeMethod(this, "queryCacheMetaAsync", Q_ARG(const QUrl&, url), Q_ARG(MiniPromise::Promise, deferred)); + } else { + auto cache = NetworkAccessManager::getInstance().cache(); + if (cache) { + QNetworkCacheMetaData metaData = cache->metaData(url); + QVariantMap attributes, rawHeaders; + if (!metaData.isValid()) { + deferred->reject("invalid cache entry", { + { "_url", url }, + { "isValid", metaData.isValid() }, + { "metaDataURL", metaData.url() }, + }); } else { - error = "cache data unavailable"; + QHashIterator i(metaData.attributes()); + while (i.hasNext()) { + i.next(); + attributes[QString::number(i.key())] = i.value(); + } + for (const auto& i : metaData.rawHeaders()) { + rawHeaders[i.first] = i.second; + } + deferred->resolve({ + { "_url", url }, + { "isValid", metaData.isValid() }, + { "url", metaData.url() }, + { "expirationDate", metaData.expirationDate() }, + { "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() }, + { "saveToDisk", metaData.saveToDisk() }, + { "attributes", attributes }, + { "rawHeaders", rawHeaders }, + }); } - deferred->handle(error, result); - }); - } else { - deferred->reject("cache currently unavailable"); + } else { + deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg("cache unavailable")); + } } + return deferred; +} + +MiniPromise::Promise AssetClient::loadFromCacheAsync(const QUrl& url, MiniPromise::Promise deferred) { + auto errorMessage = CACHE_ERROR_MESSAGE.arg(__FUNCTION__); + if (!deferred) { + deferred = makePromise(__FUNCTION__); // create on caller's thread + } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadFromCacheAsync", Q_ARG(const QUrl&, url), Q_ARG(MiniPromise::Promise, deferred)); + } else { + auto cache = NetworkAccessManager::getInstance().cache(); + if (cache) { + MiniPromise::Promise metaRequest = makePromise(__FUNCTION__); + queryCacheMetaAsync(url, metaRequest); + metaRequest->finally([&](QString error, QVariantMap metadata) { + QVariantMap result = { + { "url", url }, + { "metadata", metadata }, + { "data", QByteArray() }, + }; + if (!error.isEmpty()) { + deferred->reject(error, result); + return; + } + // caller is responsible for the deletion of the ioDevice, hence the unique_ptr + auto ioDevice = std::unique_ptr(cache->data(url)); + if (ioDevice) { + result["data"] = ioDevice->readAll(); + } else { + error = errorMessage.arg("error reading data"); + } + deferred->handle(error, result); + }); + } else { + deferred->reject(errorMessage.arg("cache unavailable")); + } + } + return deferred; } namespace { // parse RFC 1123 HTTP date format QDateTime parseHttpDate(const QString& dateString) { QDateTime dt = QDateTime::fromString(dateString.left(25), "ddd, dd MMM yyyy HH:mm:ss"); + if (!dt.isValid()) { + dt = QDateTime::fromString(dateString, Qt::ISODateWithMs); + } + if (!dt.isValid()) { + qDebug() << __FUNCTION__ << "unrecognized date format:" << dateString; + } dt.setTimeSpec(Qt::UTC); return dt; } + QDateTime getHttpDateValue(const QVariantMap& headers, const QString& keyName, const QDateTime& defaultValue) { + return headers.contains(keyName) ? parseHttpDate(headers[keyName].toString()) : defaultValue; + } } -void AssetClient::saveToCache(MiniPromise::Promise deferred, const QUrl& url, const QByteArray& data, const QVariantMap& headers) { - if (auto cache = NetworkAccessManager::getInstance().cache()) { - QDateTime lastModified = headers.contains("last-modified") ? - parseHttpDate(headers["last-modified"].toString()) : - QDateTime::currentDateTimeUtc(); - QDateTime expirationDate = headers.contains("expires") ? - parseHttpDate(headers["expires"].toString()) : - QDateTime(); // never expires - QNetworkCacheMetaData metaData; - metaData.setUrl(url); - metaData.setSaveToDisk(true); - metaData.setLastModified(lastModified); - metaData.setExpirationDate(expirationDate); - if (auto ioDevice = cache->prepare(metaData)) { - ioDevice->write(data); - cache->insert(ioDevice); - qCDebug(asset_client) << url.toDisplayString() << "saved to disk cache ("<< data.size()<<" bytes)"; - deferred->resolve({{ "success", true }}); - } else { - auto error = QString("Could not save %1 to disk cache").arg(url.toDisplayString()); - qCWarning(asset_client) << error; - deferred->reject(error); - } - } else { - deferred->reject("cache currently unavailable"); +MiniPromise::Promise AssetClient::saveToCacheAsync(const QUrl& url, const QByteArray& data, const QVariantMap& headers, MiniPromise::Promise deferred) { + if (!deferred) { + deferred = makePromise(__FUNCTION__); // create on caller's thread } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod( + this, "saveToCacheAsync", Qt::QueuedConnection, + Q_ARG(const QUrl&, url), + Q_ARG(const QByteArray&, data), + Q_ARG(const QVariantMap&, headers), + Q_ARG(MiniPromise::Promise, deferred)); + } else { + auto cache = NetworkAccessManager::getInstance().cache(); + if (cache) { + QNetworkCacheMetaData metaData; + metaData.setUrl(url); + metaData.setSaveToDisk(true); + metaData.setLastModified(getHttpDateValue(headers, "last-modified", QDateTime::currentDateTimeUtc())); + metaData.setExpirationDate(getHttpDateValue(headers, "expires", QDateTime())); // nil defaultValue == never expires + auto ioDevice = cache->prepare(metaData); + if (ioDevice) { + ioDevice->write(data); + cache->insert(ioDevice); + qCDebug(asset_client) << url.toDisplayString() << "saved to disk cache ("<< data.size()<<" bytes)"; + deferred->resolve({ + { "url", url }, + { "success", true }, + { "metaDataURL", metaData.url() }, + { "byteLength", data.size() }, + { "expirationDate", metaData.expirationDate() }, + { "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() }, + }); + } else { + auto error = QString("Could not save %1 to disk cache").arg(url.toDisplayString()); + qCWarning(asset_client) << error; + deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg(error)); + } + } else { + deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg("unavailable")); + } + } + return deferred; } void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) { diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 81149bf3d6..3ec96c3dd4 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -67,10 +67,10 @@ public slots: void init(); void cacheInfoRequest(QObject* reciever, QString slot); - void cacheInfoRequest(MiniPromise::Promise deferred); - void queryCacheMeta(MiniPromise::Promise deferred, const QUrl& url); - void loadFromCache(MiniPromise::Promise deferred, const QUrl& url); - void saveToCache(MiniPromise::Promise deferred, const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); + MiniPromise::Promise cacheInfoRequestAsync(MiniPromise::Promise deferred = nullptr); + MiniPromise::Promise queryCacheMetaAsync(const QUrl& url, MiniPromise::Promise deferred = nullptr); + MiniPromise::Promise loadFromCacheAsync(const QUrl& url, MiniPromise::Promise deferred = nullptr); + MiniPromise::Promise saveToCacheAsync(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap(), MiniPromise::Promise deferred = nullptr); void clearCache(); private slots: diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index 45150e80c1..117274eab8 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -50,7 +50,7 @@ QUrl getATPUrl(const QString& input) { QUrl::RemoveAuthority | QUrl::RemoveScheme | QUrl::StripTrailingSlash | QUrl::NormalizePathSegments ); - QString baseName = QFileInfo(path).baseName(); + QString baseName = QFileInfo(url.path()).baseName(); if (isValidPath(path) || isValidHash(baseName)) { return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP).arg(path)); } diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index f6f7fd87e3..d62e992822 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -28,14 +28,18 @@ using Promise = MiniPromise::Promise; QSharedPointer BaseAssetScriptingInterface::assetClient() { - return DependencyManager::get(); + auto client = DependencyManager::get(); + Q_ASSERT(client); + if (!client) { + qDebug() << "BaseAssetScriptingInterface::assetClient unavailable"; + } + return client; } BaseAssetScriptingInterface::BaseAssetScriptingInterface(QObject* parent) : QObject(parent) {} bool BaseAssetScriptingInterface::initializeCache() { - auto assets = assetClient(); - if (!assets) { + if (!assetClient()) { return false; // not yet possible to initialize the cache } if (!_cacheDirectory.isEmpty()) { @@ -43,41 +47,64 @@ bool BaseAssetScriptingInterface::initializeCache() { } // attempt to initialize the cache - QMetaObject::invokeMethod(assets.data(), "init"); + QMetaObject::invokeMethod(assetClient().data(), "init"); Promise deferred = makePromise("BaseAssetScriptingInterface--queryCacheStatus"); - deferred->then([&](QVariantMap result) { + deferred->then([this](QVariantMap result) { _cacheDirectory = result.value("cacheDirectory").toString(); }); - deferred->fail([&](QString error) { + deferred->fail([](QString error) { qDebug() << "BaseAssetScriptingInterface::queryCacheStatus ERROR" << QThread::currentThread() << error; }); - assets->cacheInfoRequest(deferred); + assetClient()->cacheInfoRequestAsync(deferred); return false; // cache is not ready yet } Promise BaseAssetScriptingInterface::getCacheStatus() { - Promise deferred = makePromise(__FUNCTION__); - DependencyManager::get()->cacheInfoRequest(deferred); - return deferred; + return assetClient()->cacheInfoRequestAsync(makePromise(__FUNCTION__)); } Promise BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) { - Promise deferred = makePromise(__FUNCTION__); - DependencyManager::get()->queryCacheMeta(deferred, url); - return deferred; + return assetClient()->queryCacheMetaAsync(url, makePromise(__FUNCTION__)); } -Promise BaseAssetScriptingInterface::loadFromCache(const QUrl& url) { - Promise deferred = makePromise(__FUNCTION__); - DependencyManager::get()->loadFromCache(deferred, url); - return deferred; +Promise BaseAssetScriptingInterface::loadFromCache(const QUrl& url, bool decompress, const QString& responseType) { + QVariantMap metaData = { + { "_type", "cache" }, + { "url", url }, + { "responseType", responseType }, + }; + + Promise completed = makePromise("loadFromCache::completed"); + Promise fetched = makePromise("loadFromCache::fetched"); + + Promise downloaded = assetClient()->loadFromCacheAsync(url, makePromise("loadFromCache-retrieval")); + downloaded->mixin(metaData); + downloaded->fail(fetched); + + if (decompress) { + downloaded->then([=](QVariantMap result) { + fetched->mixin(result); + Promise decompressed = decompressBytes(result.value("data").toByteArray()); + decompressed->mixin(result); + decompressed->ready(fetched); + }); + } else { + downloaded->then(fetched); + } + + fetched->fail(completed); + fetched->then([=](QVariantMap result) { + Promise converted = convertBytes(result.value("data").toByteArray(), responseType); + converted->mixin(result); + converted->ready(completed); + }); + + return completed; } Promise BaseAssetScriptingInterface::saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& headers) { - Promise deferred = makePromise(__FUNCTION__); - DependencyManager::get()->saveToCache(deferred, url, data, headers); - return deferred; + return assetClient()->saveToCacheAsync(url, data, headers, makePromise(__FUNCTION__)); } Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, QString responseType) { @@ -92,73 +119,81 @@ Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, Q { "responseType", responseType }, }; - Promise fetched = makePromise("loadAsset::fetched"), - loaded = makePromise("loadAsset::loaded"); + Promise completed = makePromise("loadAsset::completed"); + Promise fetched = makePromise("loadAsset::fetched"); - downloadBytes(hash) - ->mixin(metaData) - ->ready([=](QString error, QVariantMap result) { - Q_ASSERT(thread() == QThread::currentThread()); - fetched->mixin(result); - if (decompress) { - decompressBytes(result.value("data").toByteArray()) - ->mixin(result) - ->ready([=](QString error, QVariantMap result) { - fetched->handle(error, result); - }); - } else { - fetched->handle(error, result); - } + Promise downloaded = downloadBytes(hash); + downloaded->mixin(metaData); + downloaded->fail(fetched); + + if (decompress) { + downloaded->then([=](QVariantMap result) { + Q_ASSERT(thread() == QThread::currentThread()); + fetched->mixin(result); + Promise decompressed = decompressBytes(result.value("data").toByteArray()); + decompressed->mixin(result); + decompressed->ready(fetched); + }); + } else { + downloaded->then(fetched); + } + + fetched->fail(completed); + fetched->then([=](QVariantMap result) { + Promise converted = convertBytes(result.value("data").toByteArray(), responseType); + converted->mixin(result); + converted->ready(completed); }); - fetched->ready([=](QString error, QVariantMap result) { - if (responseType == "arraybuffer") { - loaded->resolve(NoError, result); - } else { - convertBytes(result.value("data").toByteArray(), responseType) - ->mixin(result) - ->ready([=](QString error, QVariantMap result) { - loaded->resolve(NoError, result); - }); - } - }); - - return loaded; + return completed; } Promise BaseAssetScriptingInterface::convertBytes(const QByteArray& dataByteArray, const QString& responseType) { - QVariantMap result; + QVariantMap result = { + { "_contentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() }, + { "_byteLength", dataByteArray.size() }, + { "_responseType", responseType }, + }; + QString error; Promise conversion = makePromise(__FUNCTION__); - if (dataByteArray.size() == 0) { - result["response"] = QString(); + if (!RESPONSE_TYPES.contains(responseType)) { + error = QString("convertBytes: invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | ")); + } else if (responseType == "arraybuffer") { + // interpret as bytes + result["response"] = dataByteArray; } else if (responseType == "text") { + // interpret as utf-8 text result["response"] = QString::fromUtf8(dataByteArray); } else if (responseType == "json") { + // interpret as JSON QJsonParseError status; auto parsed = QJsonDocument::fromJson(dataByteArray, &status); if (status.error == QJsonParseError::NoError) { - result["response"] = parsed.isArray() ? - QVariant(parsed.array().toVariantList()) : - QVariant(parsed.object().toVariantMap()); + result["response"] = parsed.isArray() ? QVariant(parsed.array().toVariantList()) : QVariant(parsed.object().toVariantMap()); } else { - QVariantMap errorResult = { + result = { { "error", status.error }, { "offset", status.offset }, }; - return conversion->reject("JSON Parse Error: " + status.errorString(), errorResult); + error = "JSON Parse Error: " + status.errorString(); } - } else if (responseType == "arraybuffer") { - result["response"] = dataByteArray; } - return conversion->resolve(NoError, result); + if (result.value("response").canConvert()) { + auto data = result.value("response").toByteArray(); + result["contentType"] = QMimeDatabase().mimeTypeForData(data).name(); + result["byteLength"] = data.size(); + result["responseType"] = responseType; + } + return conversion->handle(error, result); } Promise BaseAssetScriptingInterface::decompressBytes(const QByteArray& dataByteArray) { QByteArray inflated; + Promise decompressed = makePromise(__FUNCTION__); auto start = usecTimestampNow(); if (gunzip(dataByteArray, inflated)) { auto end = usecTimestampNow(); - return makePromise(__FUNCTION__)->resolve(NoError, { + decompressed->resolve({ { "_compressedByteLength", dataByteArray.size() }, { "_compressedContentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() }, { "_compressMS", (double)(end - start) / 1000.0 }, @@ -168,16 +203,18 @@ Promise BaseAssetScriptingInterface::decompressBytes(const QByteArray& dataByteA { "data", inflated }, }); } else { - return makePromise(__FUNCTION__)->reject("gunzip error", {}); + decompressed->reject("gunzip error"); } + return decompressed; } Promise BaseAssetScriptingInterface::compressBytes(const QByteArray& dataByteArray, int level) { QByteArray deflated; auto start = usecTimestampNow(); + Promise compressed = makePromise(__FUNCTION__); if (gzip(dataByteArray, deflated, level)) { auto end = usecTimestampNow(); - return makePromise(__FUNCTION__)->resolve(NoError, { + compressed->resolve({ { "_uncompressedByteLength", dataByteArray.size() }, { "_uncompressedContentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() }, { "_compressMS", (double)(end - start) / 1000.0 }, @@ -187,13 +224,13 @@ Promise BaseAssetScriptingInterface::compressBytes(const QByteArray& dataByteArr { "data", deflated }, }); } else { - return makePromise(__FUNCTION__)->reject("gzip error", {}); + compressed->reject("gzip error", {}); } + return compressed; } Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { - auto assetClient = DependencyManager::get(); - QPointer assetRequest = assetClient->createRequest(hash); + QPointer assetRequest = assetClient()->createRequest(hash); Promise deferred = makePromise(__FUNCTION__); QObject::connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) { @@ -208,7 +245,7 @@ Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { { "url", request->getUrl() }, { "hash", request->getHash() }, { "cached", request->loadedFromCache() }, - { "content-type", QMimeDatabase().mimeTypeForData(data).name() }, + { "contentType", QMimeDatabase().mimeTypeForData(data).name() }, { "data", data }, }; } else { @@ -225,8 +262,9 @@ Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { Promise BaseAssetScriptingInterface::uploadBytes(const QByteArray& bytes) { Promise deferred = makePromise(__FUNCTION__); - QPointer upload = DependencyManager::get()->createUpload(bytes); + QPointer upload = assetClient()->createUpload(bytes); + const auto byteLength = bytes.size(); QObject::connect(upload, &AssetUpload::finished, upload, [=](AssetUpload* upload, const QString& hash) { Q_ASSERT(QThread::currentThread() == upload->thread()); // note: we are now on the "Resource Manager" thread @@ -237,6 +275,7 @@ Promise BaseAssetScriptingInterface::uploadBytes(const QByteArray& bytes) { { "hash", hash }, { "url", AssetUtils::getATPUrl(hash).toString() }, { "filename", upload->getFilename() }, + { "byteLength", byteLength }, }; } else { error = upload->getErrorString(); @@ -251,20 +290,19 @@ Promise BaseAssetScriptingInterface::uploadBytes(const QByteArray& bytes) { } Promise BaseAssetScriptingInterface::getAssetInfo(QString asset) { - auto deferred = makePromise(__FUNCTION__); + Promise deferred = makePromise(__FUNCTION__); auto url = AssetUtils::getATPUrl(asset); auto path = url.path(); auto hash = AssetUtils::extractAssetHash(asset); if (AssetUtils::isValidHash(hash)) { // already a valid ATP hash -- nothing to do - deferred->resolve(NoError, { + deferred->resolve({ { "hash", hash }, { "path", path }, { "url", url }, }); } else if (AssetUtils::isValidFilePath(path)) { - auto assetClient = DependencyManager::get(); - QPointer request = assetClient->createGetMappingRequest(path); + QPointer request = assetClient()->createGetMappingRequest(path); QObject::connect(request, &GetMappingRequest::finished, request, [=]() { Q_ASSERT(QThread::currentThread() == request->thread()); @@ -276,7 +314,9 @@ Promise BaseAssetScriptingInterface::getAssetInfo(QString asset) { { "_hash", hash }, { "_path", path }, { "_url", url }, + { "url", url }, { "hash", request->getHash() }, + { "hashURL", AssetUtils::getATPUrl(request->getHash()).toString() }, { "wasRedirected", request->wasRedirected() }, { "path", request->wasRedirected() ? request->getRedirectedPath() : path }, }; @@ -297,8 +337,7 @@ Promise BaseAssetScriptingInterface::getAssetInfo(QString asset) { Promise BaseAssetScriptingInterface::symlinkAsset(QString hash, QString path) { auto deferred = makePromise(__FUNCTION__); - auto assetClient = DependencyManager::get(); - QPointer setMappingRequest = assetClient->createSetMappingRequest(path, hash); + QPointer setMappingRequest = assetClient()->createSetMappingRequest(path, hash); connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [=](SetMappingRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index 35c829fd37..5ac391a4d7 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -27,37 +27,29 @@ class BaseAssetScriptingInterface : public QObject { Q_OBJECT public: + const QStringList RESPONSE_TYPES{ "text", "arraybuffer", "json" }; using Promise = MiniPromise::Promise; QSharedPointer assetClient(); BaseAssetScriptingInterface(QObject* parent = nullptr); public slots: - Promise getCacheStatus(); - - /**jsdoc - * Initialize the disk cache (returns true if already initialized) - * @function Assets.initializeCache - * @static - */ - bool initializeCache(); - - virtual bool isValidPath(QString input) { return AssetUtils::isValidPath(input); } - virtual bool isValidFilePath(QString input) { return AssetUtils::isValidFilePath(input); } + bool isValidPath(QString input) { return AssetUtils::isValidPath(input); } + bool isValidFilePath(QString input) { return AssetUtils::isValidFilePath(input); } QUrl getATPUrl(QString input) { return AssetUtils::getATPUrl(input); } QString extractAssetHash(QString input) { return AssetUtils::extractAssetHash(input); } bool isValidHash(QString input) { return AssetUtils::isValidHash(input); } QByteArray hashData(const QByteArray& data) { return AssetUtils::hashData(data); } QString hashDataHex(const QByteArray& data) { return hashData(data).toHex(); } - virtual Promise queryCacheMeta(const QUrl& url); - virtual Promise loadFromCache(const QUrl& url); - virtual Promise saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); - protected: QString _cacheDirectory; - const QString NoError{}; - //virtual bool jsAssert(bool condition, const QString& error) = 0; + bool initializeCache(); + Promise getCacheStatus(); + Promise queryCacheMeta(const QUrl& url); + Promise loadFromCache(const QUrl& url, bool decompress = false, const QString& responseType = "arraybuffer"); + Promise saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap()); + Promise loadAsset(QString asset, bool decompress, QString responseType); Promise getAssetInfo(QString asset); Promise downloadBytes(QString hash); diff --git a/libraries/script-engine/src/ArrayBufferViewClass.cpp b/libraries/script-engine/src/ArrayBufferViewClass.cpp index cf776ed834..84cb32d665 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.cpp +++ b/libraries/script-engine/src/ArrayBufferViewClass.cpp @@ -11,7 +11,8 @@ #include "ArrayBufferViewClass.h" -Q_DECLARE_METATYPE(QByteArray*) +int qScriptClassPointerMetaTypeId = qRegisterMetaType(); +int qByteArrayMetaTypeId = qRegisterMetaType(); ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngine* scriptEngine) : QObject(scriptEngine), @@ -21,6 +22,7 @@ _scriptEngine(scriptEngine) { _bufferName = engine()->toStringHandle(BUFFER_PROPERTY_NAME.toLatin1()); _byteOffsetName = engine()->toStringHandle(BYTE_OFFSET_PROPERTY_NAME.toLatin1()); _byteLengthName = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); + registerMetaTypes(scriptEngine); } QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& object, @@ -50,3 +52,34 @@ QScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const QScriptVal const QScriptString& name, uint id) { return QScriptValue::Undeletable; } + +namespace { + void byteArrayFromScriptValue(const QScriptValue& object, QByteArray& byteArray) { + if (object.isValid()) { + if (object.isObject()) { + if (object.isArray()) { + auto Uint8Array = object.engine()->globalObject().property("Uint8Array"); + auto typedArray = Uint8Array.construct(QScriptValueList{object}); + byteArray = qvariant_cast(typedArray.property("buffer").toVariant()); + } else { + byteArray = qvariant_cast(object.data().toVariant()); + } + } else { + byteArray = object.toString().toUtf8(); + } + } + } + + QScriptValue byteArrayToScriptValue(QScriptEngine *engine, const QByteArray& byteArray) { + QScriptValue data = engine->newVariant(QVariant::fromValue(byteArray)); + QScriptValue constructor = engine->globalObject().property("ArrayBuffer"); + Q_ASSERT(constructor.isValid()); + auto array = qscriptvalue_cast(constructor.data()); + Q_ASSERT(array); + return engine->newObject(array, data); + } +} + +void ArrayBufferViewClass::registerMetaTypes(QScriptEngine* scriptEngine) { + qScriptRegisterMetaType(scriptEngine, byteArrayToScriptValue, byteArrayFromScriptValue); +} diff --git a/libraries/script-engine/src/ArrayBufferViewClass.h b/libraries/script-engine/src/ArrayBufferViewClass.h index 67af4a3fc3..038cc75ffd 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.h +++ b/libraries/script-engine/src/ArrayBufferViewClass.h @@ -29,6 +29,7 @@ static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; class ArrayBufferViewClass : public QObject, public QScriptClass { Q_OBJECT public: + static void registerMetaTypes(QScriptEngine* scriptEngine); ArrayBufferViewClass(ScriptEngine* scriptEngine); ScriptEngine* getScriptEngine() { return _scriptEngine; } @@ -49,4 +50,7 @@ protected: ScriptEngine* _scriptEngine; }; +Q_DECLARE_METATYPE(QScriptClass*) +Q_DECLARE_METATYPE(QByteArray) + #endif // hifi_ArrayBufferViewClass_h diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 0870460a41..7c9bf2c4eb 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -18,27 +18,33 @@ #include #include #include +#include #include #include #include - -#include -#include "Gzip.h" #include "ScriptEngine.h" #include "ScriptEngineLogging.h" -AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) {} +#include +#include + +using Promise = MiniPromise::Promise; + +AssetScriptingInterface::AssetScriptingInterface(QObject* parent) : BaseAssetScriptingInterface(parent) { + qCDebug(scriptengine) << "AssetScriptingInterface::AssetScriptingInterface" << parent; + MiniPromise::registerMetaTypes(parent); +} #define JS_VERIFY(cond, error) { if (!this->jsVerify(cond, error)) { return; } } void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { - auto handler = makeScopedHandlerObject(thisObject(), callback); + auto handler = jsBindCallback(thisObject(), callback); QByteArray dataByteArray = data.toUtf8(); auto upload = DependencyManager::get()->createUpload(dataByteArray); Promise deferred = makePromise(__FUNCTION__); - deferred->ready([this, handler](QString error, QVariantMap result) { + deferred->ready([=](QString error, QVariantMap result) { auto url = result.value("url").toString(); auto hash = result.value("hash").toString(); jsCallback(handler, url, hash); @@ -47,7 +53,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { connect(upload, &AssetUpload::finished, upload, [this, deferred](AssetUpload* upload, const QString& hash) { // we are now on the "Resource Manager" thread (and "hash" being a *reference* makes it unsafe to use directly) Q_ASSERT(QThread::currentThread() == upload->thread()); - deferred->resolve(NoError, { + deferred->resolve({ { "url", "atp:" + hash }, { "hash", hash }, }); @@ -57,7 +63,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) { } void AssetScriptingInterface::setMapping(QString path, QString hash, QScriptValue callback) { - auto handler = makeScopedHandlerObject(thisObject(), callback); + auto handler = jsBindCallback(thisObject(), callback); auto setMappingRequest = assetClient()->createSetMappingRequest(path, hash); Promise deferred = makePromise(__FUNCTION__); deferred->ready([=](QString error, QVariantMap result) { @@ -86,7 +92,7 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb return; } QString hash = AssetUtils::extractAssetHash(urlString); - auto handler = makeScopedHandlerObject(thisObject(), callback); + auto handler = jsBindCallback(thisObject(), callback); auto assetClient = DependencyManager::get(); auto assetRequest = assetClient->createRequest(hash); @@ -104,11 +110,11 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb if (request->getError() == AssetRequest::Error::NoError) { QString data = QString::fromUtf8(request->getData()); // forward a thread-safe values back to our thread - deferred->resolve(NoError, { { "data", data } }); + deferred->resolve({ { "data", data } }); } else { // FIXME: propagate error to scripts? (requires changing signature or inverting param order above..) //deferred->resolve(request->getErrorString(), { { "error", requet->getError() } }); - qDebug() << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString(); + qCDebug(scriptengine) << "AssetScriptingInterface::downloadData ERROR: " << request->getErrorString(); } request->deleteLater(); @@ -118,13 +124,9 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb } void AssetScriptingInterface::setBakingEnabled(QString path, bool enabled, QScriptValue callback) { - auto handler = makeScopedHandlerObject(thisObject(), callback); auto setBakingEnabledRequest = DependencyManager::get()->createSetBakingEnabledRequest({ path }, enabled); - Promise deferred = makePromise(__FUNCTION__); - deferred->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); + Promise deferred = jsPromiseReady(makePromise(__FUNCTION__), thisObject(), callback); connect(setBakingEnabledRequest, &SetBakingEnabledRequest::finished, setBakingEnabledRequest, [this, deferred](SetBakingEnabledRequest* request) { Q_ASSERT(QThread::currentThread() == request->thread()); @@ -150,13 +152,11 @@ void AssetScriptingInterface::sendFakedHandshake() { void AssetScriptingInterface::getMapping(QString asset, QScriptValue callback) { auto path = AssetUtils::getATPUrl(asset).path(); - auto handler = makeScopedHandlerObject(thisObject(), callback); + auto handler = jsBindCallback(thisObject(), callback); JS_VERIFY(AssetUtils::isValidFilePath(path), "invalid ATP file path: " + asset + "(path:"+path+")"); JS_VERIFY(callback.isFunction(), "expected second parameter to be a callback function"); - qDebug() << ">>getMapping//getAssetInfo" << path; Promise promise = getAssetInfo(path); - promise->ready([this, handler](QString error, QVariantMap result) { - qDebug() << "//getMapping//getAssetInfo" << error << result.keys(); + promise->ready([=](QString error, QVariantMap result) { jsCallback(handler, error, result.value("hash").toString()); }); } @@ -168,11 +168,31 @@ bool AssetScriptingInterface::jsVerify(bool condition, const QString& error) { if (context()) { context()->throwError(error); } else { - qDebug() << "WARNING -- jsVerify failed outside of a valid JS context: " + error; + qCDebug(scriptengine) << "WARNING -- jsVerify failed outside of a valid JS context: " + error; } return false; } +QScriptValue AssetScriptingInterface::jsBindCallback(QScriptValue scope, QScriptValue callback) { + QScriptValue handler = ::makeScopedHandlerObject(scope, callback); + QScriptValue value = handler.property("callback"); + if (!jsVerify(handler.isObject() && value.isFunction(), + QString("jsBindCallback -- .callback is not a function (%1)").arg(value.toVariant().typeName()))) { + return QScriptValue(); + } + return handler; +} + +Promise AssetScriptingInterface::jsPromiseReady(Promise promise, QScriptValue scope, QScriptValue callback) { + auto handler = jsBindCallback(scope, callback); + if (!jsVerify(handler.isValid(), "jsPromiseReady -- invalid callback handler")) { + return nullptr; + } + return promise->ready([this, handler](QString error, QVariantMap result) { + jsCallback(handler, error, result); + }); +} + void AssetScriptingInterface::jsCallback(const QScriptValue& handler, const QScriptValue& error, const QScriptValue& result) { Q_ASSERT(thread() == QThread::currentThread()); @@ -208,46 +228,36 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, responseType = "text"; } auto asset = AssetUtils::getATPUrl(url).path(); - auto handler = makeScopedHandlerObject(scope, callback); - - JS_VERIFY(handler.property("callback").isFunction(), - QString("Invalid callback function (%1)").arg(handler.property("callback").toVariant().typeName())); JS_VERIFY(AssetUtils::isValidHash(asset) || AssetUtils::isValidFilePath(asset), QString("Invalid ATP url '%1'").arg(url)); JS_VERIFY(RESPONSE_TYPES.contains(responseType), QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | "))); - Promise resolved = makePromise("resolved"); - Promise loaded = makePromise("loaded"); + Promise fetched = jsPromiseReady(makePromise("fetched"), scope, callback); + Promise mapped = makePromise("mapped"); - loaded->ready([=](QString error, QVariantMap result) { - qDebug() << "//loaded" << error; - jsCallback(handler, error, result); - }); - - resolved->ready([=](QString error, QVariantMap result) { - qDebug() << "//resolved" << result.value("hash"); + mapped->ready([=](QString error, QVariantMap result) { QString hash = result.value("hash").toString(); + QString url = result.value("url").toString(); if (!error.isEmpty() || !AssetUtils::isValidHash(hash)) { - loaded->reject(error.isEmpty() ? "internal hash error: " + hash : error, result); + fetched->reject(error.isEmpty() ? "internal hash error: " + hash : error, result); } else { Promise promise = loadAsset(hash, decompress, responseType); promise->mixin(result); - promise->ready([this, loaded, hash](QString error, QVariantMap result) { - qDebug() << "//getAssetInfo/loadAsset" << error << hash; - loaded->resolve(NoError, result); + promise->ready([=](QString error, QVariantMap loadResult) { + loadResult["url"] = url; // maintain mapped .url in results (vs. atp:hash returned by loadAsset) + fetched->handle(error, loadResult); }); } }); if (AssetUtils::isValidHash(asset)) { - resolved->resolve(NoError, { { "hash", asset } }); - } else { - Promise promise = getAssetInfo(asset); - promise->ready([this, resolved](QString error, QVariantMap result) { - qDebug() << "//getAssetInfo" << error << result.value("hash") << result.value("path"); - resolved->resolve(error, result); + mapped->resolve({ + { "hash", asset }, + { "url", url }, }); + } else { + getAssetInfo(asset)->ready(mapped); } } @@ -256,128 +266,166 @@ void AssetScriptingInterface::resolveAsset(QScriptValue options, QScriptValue sc auto url = (options.isString() ? options : options.property(URL)).toString(); auto asset = AssetUtils::getATPUrl(url).path(); - auto handler = makeScopedHandlerObject(scope, callback); JS_VERIFY(AssetUtils::isValidFilePath(asset) || AssetUtils::isValidHash(asset), "expected options to be an asset URL or request options containing .url property"); - JS_VERIFY(handler.property("callback").isFunction(), "invalid callback function"); - getAssetInfo(asset)->ready([=](QString error, QVariantMap result) { - qDebug() << "//resolveAsset/getAssetInfo" << error << result.value("hash"); - jsCallback(handler, error, result); - }); + + jsPromiseReady(getAssetInfo(asset), scope, callback); } void AssetScriptingInterface::decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { auto data = options.property("data"); QByteArray dataByteArray = qscriptvalue_cast(data); - auto handler = makeScopedHandlerObject(scope, callback); auto responseType = options.property("responseType").toString().toLower(); if (responseType.isEmpty()) { responseType = "text"; } - Promise promise = decompressBytes(dataByteArray); - promise->ready([=](QString error, QVariantMap result) { - if (responseType == "arraybuffer") { - jsCallback(handler, error, result); - } else { - Promise promise = convertBytes(result.value("data").toByteArray(), responseType); - promise->mixin(result); - promise->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); - } - }); + Promise completed = jsPromiseReady(makePromise(__FUNCTION__), scope, callback); + Promise decompressed = decompressBytes(dataByteArray); + if (responseType == "arraybuffer") { + decompressed->ready(completed); + } else { + decompressed->ready([=](QString error, QVariantMap result) { + Promise converted = convertBytes(result.value("data").toByteArray(), responseType); + converted->mixin(result); + converted->ready(completed); + }); + } } namespace { const int32_t DEFAULT_GZIP_COMPRESSION_LEVEL = -1; const int32_t MAX_GZIP_COMPRESSION_LEVEL = 9; } - void AssetScriptingInterface::compressData(QScriptValue options, QScriptValue scope, QScriptValue callback) { - - auto data = options.property("data"); - QByteArray dataByteArray = data.isString() ? - data.toString().toUtf8() : - qscriptvalue_cast(data); - auto handler = makeScopedHandlerObject(scope, callback); - auto level = options.property("level").toInt32(); - if (level < DEFAULT_GZIP_COMPRESSION_LEVEL || level > MAX_GZIP_COMPRESSION_LEVEL) { - level = DEFAULT_GZIP_COMPRESSION_LEVEL; - } - Promise promise = compressBytes(dataByteArray, level); - promise->ready([=](QString error, QVariantMap result) { - jsCallback(handler, error, result); - }); + auto data = options.property("data").isValid() ? options.property("data") : options; + QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast(data); + int level = options.property("level").isNumber() ? options.property("level").toInt32() : DEFAULT_GZIP_COMPRESSION_LEVEL; + JS_VERIFY(level >= DEFAULT_GZIP_COMPRESSION_LEVEL || level <= MAX_GZIP_COMPRESSION_LEVEL, QString("invalid .level %1").arg(level)); + jsPromiseReady(compressBytes(dataByteArray, level), scope, callback); } void AssetScriptingInterface::putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { - auto compress = options.property("compress").toBool() || - options.property("compressed").toBool(); - auto handler = makeScopedHandlerObject(scope, callback); - auto data = options.property("data"); + auto compress = options.property("compress").toBool() || options.property("compressed").toBool(); + auto data = options.isObject() ? options.property("data") : options; auto rawPath = options.property("path").toString(); auto path = AssetUtils::getATPUrl(rawPath).path(); - QByteArray dataByteArray = data.isString() ? - data.toString().toUtf8() : - qscriptvalue_cast(data); + QByteArray dataByteArray = data.isString() ? data.toString().toUtf8() : qscriptvalue_cast(data); JS_VERIFY(path.isEmpty() || AssetUtils::isValidFilePath(path), QString("expected valid ATP file path '%1' ('%2')").arg(rawPath).arg(path)); - JS_VERIFY(handler.property("callback").isFunction(), - "invalid callback function"); JS_VERIFY(dataByteArray.size() > 0, - QString("expected non-zero .data (got %1 / #%2 bytes)") - .arg(data.toVariant().typeName()) - .arg(dataByteArray.size())); + QString("expected non-zero .data (got %1 / #%2 bytes)").arg(data.toVariant().typeName()).arg(dataByteArray.size())); // [compressed] => uploaded to server => [mapped to path] Promise prepared = makePromise("putAsset::prepared"); Promise uploaded = makePromise("putAsset::uploaded"); - Promise finished = makePromise("putAsset::finished"); + Promise completed = makePromise("putAsset::completed"); + jsPromiseReady(completed, scope, callback); if (compress) { - qDebug() << "putAsset::compressBytes..."; - Promise promise = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL); - promise->finally([=](QString error, QVariantMap result) { - qDebug() << "//putAsset::compressedBytes" << error << result.keys(); - prepared->handle(error, result); - }); + Promise compress = compressBytes(dataByteArray, DEFAULT_GZIP_COMPRESSION_LEVEL); + compress->ready(prepared); } else { - prepared->resolve(NoError, {{ "data", dataByteArray }}); + prepared->resolve({{ "data", dataByteArray }}); } - prepared->ready([=](QString error, QVariantMap result) { - qDebug() << "//putAsset::prepared" << error << result.value("data").toByteArray().size() << result.keys(); - Promise promise = uploadBytes(result.value("data").toByteArray()); - promise->mixin(result); - promise->ready([=](QString error, QVariantMap result) { - qDebug() << "===========//putAsset::prepared/uploadBytes" << error << result.keys(); - uploaded->handle(error, result); + prepared->fail(completed); + prepared->then([=](QVariantMap result) { + Promise upload = uploadBytes(result.value("data").toByteArray()); + upload->mixin(result); + upload->ready(uploaded); + }); + + uploaded->fail(completed); + if (path.isEmpty()) { + uploaded->then(completed); + } else { + uploaded->then([=](QVariantMap result) { + QString hash = result.value("hash").toString(); + if (!AssetUtils::isValidHash(hash)) { + completed->reject("path mapping requested, but did not receive valid hash", result); + } else { + Promise link = symlinkAsset(hash, path); + link->mixin(result); + link->ready(completed); + } }); - }); - - uploaded->ready([=](QString error, QVariantMap result) { - QString hash = result.value("hash").toString(); - qDebug() << "//putAsset::uploaded" << error << hash << result.keys(); - if (path.isEmpty()) { - finished->handle(error, result); - } else if (!AssetUtils::isValidHash(hash)) { - finished->reject("path mapping requested, but did not receive valid hash", result); - } else { - qDebug() << "symlinkAsset" << hash << path << QThread::currentThread(); - Promise promise = symlinkAsset(hash, path); - promise->mixin(result); - promise->ready([=](QString error, QVariantMap result) { - finished->handle(error, result); - qDebug() << "//symlinkAsset" << hash << path << result.keys(); - }); - } - }); - - finished->ready([=](QString error, QVariantMap result) { - qDebug() << "//putAsset::finished" << error << result.keys(); - jsCallback(handler, error, result); - }); + } +} + +void AssetScriptingInterface::queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback) { + QString url = options.isString() ? options.toString() : options.property("url").toString(); + JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url)); + jsPromiseReady(Parent::queryCacheMeta(url), scope, callback); +} + +void AssetScriptingInterface::loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback) { + QString url, responseType; + bool decompress = false; + if (options.isString()) { + url = options.toString(); + responseType = "text"; + } else { + url = options.property("url").toString(); + responseType = options.property("responseType").isValid() ? options.property("responseType").toString() : "text"; + decompress = options.property("decompress").toBool() || options.property("compressed").toBool(); + } + JS_VERIFY(QUrl(url).isValid(), QString("Invalid URL '%1'").arg(url)); + JS_VERIFY(RESPONSE_TYPES.contains(responseType), + QString("Invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | "))); + + jsPromiseReady(Parent::loadFromCache(url, decompress, responseType), scope, callback); +} + +bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) { + auto scriptEngine = qobject_cast(engine()); + if (!scriptEngine) { + qCDebug(scriptengine) << __FUNCTION__ << "invalid script engine" << url; + return false; + } + // allow cache writes only from Client, EntityServer and Agent scripts + bool isAllowedContext = ( + scriptEngine->isClientScript() || + scriptEngine->isAgentScript() || + scriptEngine->isEntityServerScript() + ); + if (!isAllowedContext) { + qCDebug(scriptengine) << __FUNCTION__ << "invalid context" << scriptEngine->getContext() << url; + return false; + } + return true; +} + +void AssetScriptingInterface::saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback) { + JS_VERIFY(options.isObject(), QString("expected options object as first parameter not: %1").arg(options.toVariant().typeName())); + + QString url = options.property("url").toString(); + QByteArray data = qscriptvalue_cast(options.property("data")); + QVariantMap headers = qscriptvalue_cast(options.property("headers")); + + saveToCache(url, data, headers, scope, callback); +} + +void AssetScriptingInterface::saveToCache(const QUrl& rawURL, const QByteArray& data, const QVariantMap& metadata, QScriptValue scope, QScriptValue callback) { + QUrl url = rawURL; + if (url.path().isEmpty() && !data.isEmpty()) { + // generate a valid ATP URL from the data -- appending any existing fragment or querystring values + auto atpURL = AssetUtils::getATPUrl(hashDataHex(data)); + atpURL.setQuery(url.query()); + atpURL.setFragment(url.fragment()); + qCDebug(scriptengine) << "autogenerated ATP URL" << url << "=>" << atpURL; + url = atpURL; + } + auto hash = AssetUtils::extractAssetHash(url.toDisplayString()); + + JS_VERIFY(url.isValid(), QString("Invalid URL '%1'").arg(url.toString())); + JS_VERIFY(canWriteCacheValue(url), "Invalid cache write URL: " + url.toString()); + JS_VERIFY(url.scheme() == "atp" || url.scheme() == "cache", "only 'atp' and 'cache' URL schemes supported"); + JS_VERIFY(hash.isEmpty() || hash == hashDataHex(data), QString("invalid checksum hash for atp:HASH style URL (%1 != %2)").arg(hash, hashDataHex(data))); + + qCDebug(scriptengine) << "saveToCache" << url.toDisplayString() << data << hash << metadata; + + jsPromiseReady(Parent::saveToCache(url, data, metadata), scope, callback); } diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 7001c64634..fdce173dfe 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -21,6 +21,7 @@ #include #include #include +#include #include /**jsdoc @@ -29,6 +30,7 @@ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable { Q_OBJECT public: + using Parent = BaseAssetScriptingInterface; AssetScriptingInterface(QObject* parent = nullptr); /**jsdoc @@ -102,26 +104,91 @@ public: Q_INVOKABLE void sendFakedHandshake(); #endif - // Advanced APIs - // getAsset(options, scope[callback(error, result)]) -- fetches an Asset from the Server - // [options.url] an "atp:" style URL, hash, or relative mapped path to fetch - // [options.responseType] the desired reponse type (text | arraybuffer | json) - // [options.decompress] whether to apply gunzip decompression on the stream - // [scope[callback]] continuation-style (error, { responseType, data, byteLength, ... }) callback - const QStringList RESPONSE_TYPES{ "text", "arraybuffer", "json" }; + /**jsdoc + * Request Asset data from the ATP Server + * @function Assets.getAsset + * @param {URL|Assets.GetOptions} options An atp: style URL, hash, or relative mapped path; or an {@link Assets.GetOptions} object with request parameters + * @param {Assets~getAssetCallback} scope[callback] A scope callback function to receive (error, results) values + */ + /**jsdoc + * A set of properties that can be passed to {@link Assets.getAsset}. + * @typedef {Object} Assets.GetOptions + * @property {URL} [url] an "atp:" style URL, hash, or relative mapped path to fetch + * @property {string} [responseType=text] the desired reponse type (text | arraybuffer | json) + * @property {boolean} [decompress=false] whether to attempt gunzip decompression on the fetched data + * See: {@link Assets.putAsset} and its .compress=true option + */ + /**jsdoc + * Called when Assets.getAsset is complete. + * @callback Assets~getAssetCallback + * @param {string} error - contains error message or null value if no error occured fetching the asset + * @param {Asset~getAssetResult} result - result object containing, on success containing asset metadata and contents + */ + /**jsdoc + * Result value returned by {@link Assets.getAsset}. + * @typedef {Object} Assets~getAssetResult + * @property {url} [url] the resolved "atp:" style URL for the fetched asset + * @property {string} [hash] the resolved hash for the fetched asset + * @property {string|ArrayBuffer|Object} [response] response data (possibly converted per .responseType value) + * @property {string} [responseType] response type (text | arraybuffer | json) + * @property {string} [contentType] detected asset mime-type (autodetected) + * @property {number} [byteLength] response data size in bytes + * @property {number} [decompressed] flag indicating whether data was decompressed + */ Q_INVOKABLE void getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); - // putAsset(options, scope[callback(error, result)]) -- upload a new Aset to the Server - // [options.data] -- (ArrayBuffer|String) - // [options.compress] -- (true|false) - // [options.path=undefined] -- option path mapping to set on the created hash result - // [ + + /**jsdoc + * Upload Asset data to the ATP Server + * @function Assets.putAsset + * @param {Assets.PutOptions} options A PutOptions object with upload parameters + * @param {Assets~putAssetCallback} scope[callback] A scoped callback function invoked with (error, results) + */ + /**jsdoc + * A set of properties that can be passed to {@link Assets.putAsset}. + * @typedef {Object} Assets.PutOptions + * @property {ArrayBuffer|string} [data] byte buffer or string value representing the new asset's content + * @property {string} [path=null] ATP path mapping to automatically create (upon successful upload to hash) + * @property {boolean} [compress=false] whether to gzip compress data before uploading + */ + /**jsdoc + * Called when Assets.putAsset is complete. + * @callback Assets~puttAssetCallback + * @param {string} error - contains error message (or null value if no error occured while uploading/mapping the new asset) + * @param {Asset~putAssetResult} result - result object containing error or result status of asset upload + */ + /**jsdoc + * Result value returned by {@link Assets.putAsset}. + * @typedef {Object} Assets~putAssetResult + * @property {url} [url] the resolved "atp:" style URL for the uploaded asset (based on .path if specified, otherwise on the resulting ATP hash) + * @property {string} [path] the uploaded asset's resulting ATP path (or undefined if no path mapping was assigned) + * @property {string} [hash] the uploaded asset's resulting ATP hash + * @property {boolean} [compressed] flag indicating whether the data was compressed before upload + * @property {number} [byteLength] flag indicating final byte size of the data uploaded to the ATP server + */ Q_INVOKABLE void putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); Q_INVOKABLE void resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); Q_INVOKABLE void decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); Q_INVOKABLE void compressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE bool initializeCache() { return Parent::initializeCache(); } + + Q_INVOKABLE bool canWriteCacheValue(const QUrl& url); + + Q_INVOKABLE void getCacheStatus(QScriptValue scope, QScriptValue callback = QScriptValue()) { + jsPromiseReady(Parent::getCacheStatus(), scope, callback); + } + + Q_INVOKABLE void queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + Q_INVOKABLE void saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata, + QScriptValue scope, QScriptValue callback = QScriptValue()); protected: + QScriptValue jsBindCallback(QScriptValue scope, QScriptValue callback = QScriptValue()); + Promise jsPromiseReady(Promise promise, QScriptValue scope, QScriptValue callback = QScriptValue()); + void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QVariantMap& result); void jsCallback(const QScriptValue& handler, const QScriptValue& error, const QScriptValue& result); bool jsVerify(bool condition, const QString& error); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c7034adf35..65ef2025d9 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -56,6 +56,7 @@ #include #include "ArrayBufferViewClass.h" +#include "AssetScriptingInterface.h" #include "BatchLoader.h" #include "BaseScriptEngine.h" #include "DataViewClass.h" @@ -175,6 +176,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const _timerFunctionMap(), _fileNameString(fileNameString), _arrayBufferClass(new ArrayBufferClass(this)), + _assetScriptingInterface(new AssetScriptingInterface(this)), // don't delete `ScriptEngines` until all `ScriptEngine`s are gone _scriptEngines(DependencyManager::get()) { @@ -704,7 +706,7 @@ void ScriptEngine::init() { // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); - registerGlobalObject("Assets", &_assetScriptingInterface); + registerGlobalObject("Assets", _assetScriptingInterface); registerGlobalObject("Resources", DependencyManager::get().data()); registerGlobalObject("DebugDraw", &DebugDraw::getInstance()); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 17c0e0713a..7f69eee990 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -321,7 +321,7 @@ protected: ArrayBufferClass* _arrayBufferClass; - AssetScriptingInterface _assetScriptingInterface{ this }; + AssetScriptingInterface* _assetScriptingInterface; std::function _emitScriptUpdates{ []() { return true; } }; diff --git a/libraries/shared/src/BaseScriptEngine.cpp b/libraries/shared/src/BaseScriptEngine.cpp index c92d629b75..22ae01d72f 100644 --- a/libraries/shared/src/BaseScriptEngine.cpp +++ b/libraries/shared/src/BaseScriptEngine.cpp @@ -325,6 +325,12 @@ QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue } else if (methodOrName.isFunction()) { scope = scopeOrCallback; callback = methodOrName; + } else if (!methodOrName.isValid()) { + // instantiate from an existing scoped handler object + if (scopeOrCallback.property("callback").isFunction()) { + scope = scopeOrCallback.property("scope"); + callback = scopeOrCallback.property("callback"); + } } } auto handler = engine->newObject(); diff --git a/libraries/shared/src/shared/MiniPromises.cpp b/libraries/shared/src/shared/MiniPromises.cpp index faada3627a..bb78852c29 100644 --- a/libraries/shared/src/shared/MiniPromises.cpp +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -7,4 +7,21 @@ // #include "MiniPromises.h" +#include +#include + int MiniPromise::metaTypeID = qRegisterMetaType("MiniPromise::Promise"); + +namespace { + void promiseFromScriptValue(const QScriptValue& object, MiniPromise::Promise& promise) { + Q_ASSERT(false); + } + QScriptValue promiseToScriptValue(QScriptEngine *engine, const MiniPromise::Promise& promise) { + return engine->newQObject(promise.get()); + } +} +void MiniPromise::registerMetaTypes(QObject* engine) { + auto scriptEngine = qobject_cast(engine); + qDebug() << "----------------------- MiniPromise::registerMetaTypes ------------" << scriptEngine; + qScriptRegisterMetaType(scriptEngine, promiseToScriptValue, promiseFromScriptValue); +} diff --git a/libraries/shared/src/shared/MiniPromises.h b/libraries/shared/src/shared/MiniPromises.h index 2f17760aa8..5983f135b7 100644 --- a/libraries/shared/src/shared/MiniPromises.h +++ b/libraries/shared/src/shared/MiniPromises.h @@ -32,6 +32,9 @@ class MiniPromise : public QObject, public std::enable_shared_from_this, public ReadWriteLockable { Q_OBJECT + Q_PROPERTY(QString state READ getStateString) + Q_PROPERTY(QString error READ getError) + Q_PROPERTY(QVariantMap result READ getResult) public: using HandlerFunction = std::function; using SuccessFunction = std::function; @@ -39,23 +42,25 @@ public: using HandlerFunctions = QVector; using Promise = std::shared_ptr; + static void registerMetaTypes(QObject* engine); static int metaTypeID; MiniPromise() {} MiniPromise(const QString debugName) { setObjectName(debugName); } ~MiniPromise() { - if (!_rejected && !_resolved) { - qWarning() << "MiniPromise::~MiniPromise -- destroying unhandled promise:" << objectName() << _error << _result; + if (getStateString() == "pending") { + qWarning() << "MiniPromise::~MiniPromise -- destroying pending promise:" << objectName() << _error << _result << "handlers:" << getPendingHandlerCount(); } } Promise self() { return shared_from_this(); } - Q_INVOKABLE void executeOnPromiseThread(std::function function) { + Q_INVOKABLE void executeOnPromiseThread(std::function function, MiniPromise::Promise root = nullptr) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( this, "executeOnPromiseThread", Qt::QueuedConnection, - Q_ARG(std::function, function)); + Q_ARG(std::function, function), + Q_ARG(MiniPromise::Promise, self())); } else { function(); } @@ -92,9 +97,7 @@ public: }); } else { executeOnPromiseThread([&]{ - withReadLock([&]{ - always(_error, _result); - }); + always(getError(), getResult()); }); } return self(); @@ -112,9 +115,7 @@ public: }); } else { executeOnPromiseThread([&]{ - withReadLock([&]{ - failFunc(_error, _result); - }); + failFunc(getError(), getResult()); }); } return self(); @@ -132,9 +133,7 @@ public: }); } else { executeOnPromiseThread([&]{ - withReadLock([&]{ - successFunc(_error, _result); - }); + successFunc(getError(), getResult()); }); } return self(); @@ -151,6 +150,26 @@ public: return self(); } + + // helper functions for forwarding results on to a next Promise + Promise ready(Promise next) { return finally(next); } + Promise finally(Promise next) { + return finally([next](QString error, QVariantMap result) { + next->handle(error, result); + }); + } + Promise fail(Promise next) { + return fail([next](QString error, QVariantMap result) { + next->reject(error, result); + }); + } + Promise then(Promise next) { + return then([next](QString error, QVariantMap result) { + next->resolve(error, result); + }); + } + + // trigger methods // handle() automatically resolves or rejects the promise (based on whether an error value occurred) Promise handle(QString error, const QVariantMap& result) { @@ -168,17 +187,15 @@ public: Promise resolve(QString error, const QVariantMap& result) { setState(true, error, result); - QString localError; - QVariantMap localResult; - HandlerFunctions resolveHandlers; - HandlerFunctions finallyHandlers; - withReadLock([&]{ - localError = _error; - localResult = _result; - resolveHandlers = _onresolve; - finallyHandlers = _onfinally; - }); executeOnPromiseThread([&]{ + const QString localError{ getError() }; + const QVariantMap localResult{ getResult() }; + HandlerFunctions resolveHandlers; + HandlerFunctions finallyHandlers; + withReadLock([&]{ + resolveHandlers = _onresolve; + finallyHandlers = _onfinally; + }); for (const auto& onresolve : resolveHandlers) { onresolve(localError, localResult); } @@ -195,17 +212,15 @@ public: Promise reject(QString error, const QVariantMap& result) { setState(false, error, result); - QString localError; - QVariantMap localResult; - HandlerFunctions rejectHandlers; - HandlerFunctions finallyHandlers; - withReadLock([&]{ - localError = _error; - localResult = _result; - rejectHandlers = _onreject; - finallyHandlers = _onfinally; - }); executeOnPromiseThread([&]{ + const QString localError{ getError() }; + const QVariantMap localResult{ getResult() }; + HandlerFunctions rejectHandlers; + HandlerFunctions finallyHandlers; + withReadLock([&]{ + rejectHandlers = _onreject; + finallyHandlers = _onfinally; + }); for (const auto& onreject : rejectHandlers) { onreject(localError, localResult); } @@ -224,13 +239,25 @@ private: } else { _rejected = true; } - withWriteLock([&]{ - _error = error; - }); + setError(error); assignResult(result); return self(); } + void setError(const QString error) { withWriteLock([&]{ _error = error; }); } + QString getError() const { return resultWithReadLock([this]() -> QString { return _error; }); } + QVariantMap getResult() const { return resultWithReadLock([this]() -> QVariantMap { return _result; }); } + int getPendingHandlerCount() const { + return resultWithReadLock([this]() -> int { + return _onresolve.size() + _onreject.size() + _onfinally.size(); + }); + } + QString getStateString() const { + return _rejected ? "rejected" : + _resolved ? "resolved" : + getPendingHandlerCount() ? "pending" : + "unknown"; + } QString _error; QVariantMap _result; std::atomic _rejected{false}; @@ -240,8 +267,12 @@ private: HandlerFunctions _onfinally; }; +Q_DECLARE_METATYPE(MiniPromise::Promise) + inline MiniPromise::Promise makePromise(const QString& hint = QString()) { + if (!QMetaType::isRegistered(qMetaTypeId())) { + int type = qRegisterMetaType(); + qDebug() << "makePromise -- registered MetaType:" << type; + } return std::make_shared(hint); } - -Q_DECLARE_METATYPE(MiniPromise::Promise) From 51d8b9aea688f936591dce79f40d9a209422bb7f Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 23 Jan 2018 03:12:50 -0500 Subject: [PATCH 43/73] update and expand on js unit tests --- .../tests/unit_tests/assetUnitTests.js | 383 ++++++++++++++++-- 1 file changed, 352 insertions(+), 31 deletions(-) diff --git a/scripts/developer/tests/unit_tests/assetUnitTests.js b/scripts/developer/tests/unit_tests/assetUnitTests.js index be8710e50d..5d43eaf1de 100644 --- a/scripts/developer/tests/unit_tests/assetUnitTests.js +++ b/scripts/developer/tests/unit_tests/assetUnitTests.js @@ -1,34 +1,46 @@ /* eslint-env jasmine */ +if (!Entities.canWriteAssets()) { + Window.alert('!Entities.canWriteAssets -- please goto a domain with asset rights and reload this test script'); + throw new Error('!Entities.canWriteAssets'); +} + instrument_testrunner(true); describe("Assets", function () { var context = { + memo: {}, + cache: {}, definedHash: null, definedPath: null, definedContent: null, }; + var ATP_TIMEOUT_MS = 1000; // ms + var NULL_HASH = new Array(64+1).join('0'); // 64 hex var SAMPLE_FOLDER = '/assetUnitTests'; var SAMPLE_FILE_PATH = SAMPLE_FOLDER + "/test.json"; var SAMPLE_CONTENTS = 'Test Run on ' + JSON.stringify(Date()); + var SAMPLE_JSON = JSON.stringify({ content: SAMPLE_CONTENTS }); + var SAMPLE_FLOATS = [ Math.PI, 1.1, 2.2, 3.3 ]; + var SAMPLE_BUFFER = new Float32Array(SAMPLE_FLOATS).buffer; + var IS_ASSET_HASH_REGEX = /^[a-f0-9]{64}$/i; var IS_ASSET_URL = /^atp:/; - it('Entities.canWriteAssets', function() { expect(Entities.canWriteAssets()).toBe(true); }); - it('Assets.extractAssetHash(input)', function() { + describe('extractAssetHash(input)', function() { // new helper method that provides a catch-all way to get at the sha256 hash // considering the multiple, different ways ATP hash URLs are found across the // system and in content. - + var POSITIVE_TEST_URLS = [ 'atp:HASH', 'atp:HASH.obj', + 'atp:HASH.fbx#cache-buster', 'atp:HASH.fbx?cache-buster', - 'atp:/.baked/HASH/asset.fbx', 'HASH' ]; var NEGATIVE_TEST_URLS = [ @@ -36,25 +48,278 @@ describe("Assets", function () { 'http://domain.tld', '/path/filename.fbx', 'atp:/path/filename.fbx?#HASH', + 'atp:/.baked/HASH/asset.fbx', '' ]; - pending(); + it("POSITIVE_TEST_URLS", function() { + POSITIVE_TEST_URLS.forEach(function(url) { + url = url.replace('HASH', NULL_HASH); + expect([url, Assets.extractAssetHash(url)].join('|')).toEqual([url, NULL_HASH].join('|')); + }); + }); + it("NEGATIVE_TEST_URLS", function() { + NEGATIVE_TEST_URLS.forEach(function(url) { + expect(Assets.extractAssetHash(url.replace('HASH', NULL_HASH))).toEqual(''); + }); + }); }); // new APIs + describe('.putAsset', function() { + it("data", function(done) { + Assets.putAsset(SAMPLE_CONTENTS, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.hash).toMatch(IS_ASSET_HASH_REGEX); + context.memo.stringURL = result.url; + done(); + }); + }); + it('nopath.text', function(done) { + Assets.putAsset({ + data: SAMPLE_CONTENTS, + }, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.hash).toMatch(IS_ASSET_HASH_REGEX); + context.memo.textHash = result.hash; + context.memo.textURL = result.url; + done(); + }); + }); + it('path.text', function(done) { + var samplePath = SAMPLE_FOLDER + '/content.json'; + Assets.putAsset({ + path: samplePath, + data: SAMPLE_JSON, + }, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.path).toEqual(samplePath); + expect(result.hash).toMatch(IS_ASSET_HASH_REGEX); + context.memo.jsonPath = result.path; + done(); + }); + }); + it('path.buffer', function(done) { + var samplePath = SAMPLE_FOLDER + '/content.buffer'; + Assets.putAsset({ + path: samplePath, + data: SAMPLE_BUFFER, + }, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.path).toEqual(samplePath); + expect(result.hash).toMatch(IS_ASSET_HASH_REGEX); + expect(result.byteLength).toEqual(SAMPLE_BUFFER.byteLength); + context.memo.bufferURL = result.url; + context.memo.bufferHash = result.hash; + done(); + }); + }); + it('path.json.gz', function(done) { + var samplePath = SAMPLE_FOLDER + '/content.json.gz'; + Assets.putAsset({ + path: samplePath, + data: SAMPLE_JSON, + compress: true, + }, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.path).toEqual(samplePath); + expect(result.hash).toMatch(IS_ASSET_HASH_REGEX); + context.memo.jsonCompressedPath = result.path; + done(); + }); + }); + }); + // it('.deleteAsset(options, {callback(error, result)})', function() { pending(); }); + it('.resolveAsset', function(done) { + expect(context.memo.bufferURL).toBeDefined(); + Assets.resolveAsset(context.memo.bufferURL, function(error, result) { + expect(error).toBe(null); + expect(result.url).toEqual(context.memo.bufferURL); + expect(result.hash).toEqual(context.memo.bufferHash); + done(); + }); + }); + describe('.getAsset', function() { + it('path/', function(done) { + expect(context.memo.jsonPath).toBeDefined(); + Assets.getAsset(context.memo.jsonPath, function(error, result) { + expect(error).toBe(null); + expect(result.response).toEqual(SAMPLE_JSON); + done(); + }); + }); + it('text', function(done) { + expect(context.memo.textURL).toBeDefined(); + Assets.getAsset({ + url: context.memo.textURL, + responseType: 'text', + }, function(error, result) { + expect(error).toBe(null); + expect(result.response).toEqual(SAMPLE_CONTENTS); + expect(result.url).toEqual(context.memo.textURL); + done(); + }); + }); + it('arraybuffer', function(done) { + expect(context.memo.bufferURL).toBeDefined(); + Assets.getAsset({ + url: context.memo.bufferURL, + responseType: 'arraybuffer', + }, function(error, result) { + expect(error).toBe(null); + expect(result.response.byteLength).toEqual(SAMPLE_BUFFER.byteLength); + var expected = [].slice.call(new Float32Array(SAMPLE_BUFFER)).join(','); + var actual = [].slice.call(new Float32Array(result.response)).join(','); + expect(actual).toEqual(expected); + done(); + }); + }); + it('json', function(done) { + expect(context.memo.jsonPath).toBeDefined(); + Assets.getAsset({ + url: context.memo.jsonPath, + responseType: 'json', + }, function(error, result) { + expect(error).toBe(null); + expect(result.response.content).toEqual(SAMPLE_CONTENTS); + done(); + }); + }); + it('json.gz', function(done) { + expect(context.memo.jsonCompressedPath).toBeDefined(); + Assets.getAsset({ + url: context.memo.jsonCompressedPath, + responseType: 'json', + decompress: true, + }, function(error, result) { + expect(error).toBe(null); + expect(result.decompressed).toBe(true); + expect(result.response.content).toEqual(SAMPLE_CONTENTS); + done(); + }); + }); + }); - it('Assets.getAsset(options, {callback(error, result)})', function() { pending(); }); - it('Assets.putAsset(options, {callback(error, result)})', function() { pending(); }); - it('Assets.deleteAsset(options, {callback(error, result)})', function() { pending(); }); - it('Assets.resolveAsset(options, {callback(error, result)})', function() { pending(); }); + // cache APIs + it('.getCacheStatus', function(done) { + Assets.getCacheStatus(function(error, result) { + expect(error).toBe(null); + expect(result.cacheSize).toBeGreaterThan(0); + expect(result.maximumCacheSize).toBeGreaterThan(0); + done(); + }); + }); + describe('.saveToCache', function() { + it(".data", function(done) { + Assets.saveToCache({ data: SAMPLE_CONTENTS }, function(error, result) { + expect(error).toBe(null); + expect(result.success).toBe(true); + context.cache.textURL = result.url; + done(); + }); + }); + it('.url', function(done) { + var sampleURL = 'atp:' + SAMPLE_FOLDER + '/cache.json'; + Assets.saveToCache({ + url: sampleURL, + data: SAMPLE_JSON, + }, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.url).toEqual(sampleURL); + expect(result.success).toBe(true); + context.cache.jsonURL = result.url; + done(); + }); + }); + it('.buffer', function(done) { + var sampleURL = 'atp:' + SAMPLE_FOLDER + '/cache.buffer'; + Assets.saveToCache({ + url: sampleURL, + data: SAMPLE_BUFFER, + }, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.url).toEqual(sampleURL); + expect(result.success).toBe(true); + expect(result.byteLength).toEqual(SAMPLE_BUFFER.byteLength); + context.cache.bufferURL = result.url; + done(); + }); + }); + }); + it('.queryCacheMeta', function(done) { + expect(context.cache.bufferURL).toBeDefined(); + Assets.queryCacheMeta(context.cache.bufferURL, function(error, result) { + expect(error).toBe(null); + expect(result.url).toMatch(IS_ASSET_URL); + expect(result.url).toEqual(context.cache.bufferURL); + done(); + }); + }); + describe('.loadFromCache', function() { + it('urlString', function(done) { + expect(context.cache.jsonURL).toBeDefined(); + Assets.loadFromCache(context.cache.jsonURL, function(error, result) { + expect(error).toBe(null); + expect(result.response).toEqual(SAMPLE_JSON); + done(); + }); + }); + it('.responseType=text', function(done) { + expect(context.cache.textURL).toBeDefined(); + Assets.loadFromCache({ + url: context.cache.textURL, + responseType: 'text', + }, function(error, result) { + expect(error).toBe(null); + expect(result.response).toEqual(SAMPLE_CONTENTS); + done(); + }); + }); + it('.responseType=arraybuffer', function(done) { + expect(context.cache.bufferURL).toBeDefined(); + Assets.loadFromCache({ + url: context.cache.bufferURL, + responseType: 'arraybuffer', + }, function(error, result) { + expect(error).toBe(null); + expect(result.response.byteLength).toEqual(SAMPLE_BUFFER.byteLength); + var expected = [].slice.call(new Float32Array(SAMPLE_BUFFER)).join(','); + var actual = [].slice.call(new Float32Array(result.response)).join(','); + expect(actual).toEqual(expected); + done(); + }); + }); + it('.responseType=json', function(done) { + expect(context.cache.jsonURL).toBeDefined(); + Assets.loadFromCache({ + url: context.cache.jsonURL, + responseType: 'json', + }, function(error, result) { + expect(error).toBe(null); + expect(result.response.content).toEqual(SAMPLE_CONTENTS); + done(); + }); + }); + }); - - // existing APIs - it('Assets.getATPUrl(input)', function() { pending(); }); - it('Assets.isValidPath(input)', function() { pending(); }); - it('Assets.isValidFilePath(input)', function() { pending(); }); - it('Assets.isValidHash(input)', function() { pending(); }); - it('Assets.setBakingEnabled(path, enabled, {callback(error)})', function() { pending(); }); + // existing newly-mapped APIs + it('.getATPUrl', function() { + expect(Assets.getATPUrl(SAMPLE_FILE_PATH)).toEqual('atp:' + SAMPLE_FILE_PATH); + expect(Assets.getATPUrl(NULL_HASH)).toEqual('atp:' + NULL_HASH); + expect(Assets.getATPUrl("/file.ext?a=b#c=d")).toEqual('atp:/file.ext?a=b#c=d'); + expect(Assets.getATPUrl("atp:xxx")).toEqual(''); + expect(Assets.getATPUrl("xxx")).toEqual(''); + }); + xit('.isValidPath', function(done) { pending(); }); + xit('.isValidFilePath', function() { pending(); }); + xit('.isValidHash', function() { pending(); }); + xit('.setBakingEnabled', function() { pending(); }); it("Assets.uploadData(data, {callback(url, hash)})", function (done) { Assets.uploadData(SAMPLE_CONTENTS, function(url, hash) { @@ -68,7 +333,7 @@ describe("Assets", function () { }); }); - it("Assets.setMapping(path, hash {callback(error)})", function (done) { + it("Assets.setMapping", function (done) { expect(context.definedHash).toMatch(IS_ASSET_HASH_REGEX); Assets.setMapping(SAMPLE_FILE_PATH, context.definedHash, function(error) { if (error) error += ' ('+JSON.stringify([SAMPLE_FILE_PATH, context.definedHash])+')'; @@ -79,7 +344,7 @@ describe("Assets", function () { }); }); - it("Assets.getMapping(path, {callback(error, hash)})", function (done) { + it("Assets.getMapping", function (done) { expect(context.definedHash).toMatch(IS_ASSET_HASH_REGEX, 'asfasdf'); expect(context.definedPath).toMatch(/^\//, 'asfasdf'); Assets.getMapping(context.definedPath, function(error, hash) { @@ -91,36 +356,47 @@ describe("Assets", function () { }); }); - it('Assets.getMapping(nullHash, {callback(data)})', function(done) { + it('.getMapping(nullHash)', function(done) { // FIXME: characterization test -- current behavior is that downloadData silently fails // when given an asset that doesn't exist Assets.downloadData(NULL_HASH, function(result) { throw new Error("this callback 'should' not be triggered"); }); - setTimeout(function() { done(); }, 2000); + setTimeout(function() { done(); }, ATP_TIMEOUT_MS); }); - it('Assets.downloadData(hash, {callback(data)})', function(done) { + it('.downloadData(hash)', function(done) { expect(context.definedHash).toMatch(IS_ASSET_HASH_REGEX, 'asfasdf'); Assets.downloadData('atp:' + context.definedHash, function(result) { expect(result).toEqual(context.definedContent); done(); }); }); - - it('Assets.downloadData(nullHash, {callback(data)})', function(done) { + + it('.downloadData(nullHash)', function(done) { // FIXME: characterization test -- current behavior is that downloadData silently fails // when given an asset doesn't exist Assets.downloadData(NULL_HASH, function(result) { throw new Error("this callback 'should' not be triggered"); }); - setTimeout(function() { - done(); - }, 2000); + setTimeout(function() { done(); }, ATP_TIMEOUT_MS); }); - + describe('exceptions', function() { - describe('Asset.setMapping', function() { + describe('.getAsset', function() { + it('-- invalid data.gz', function(done) { + expect(context.memo.jsonPath).toBeDefined(); + Assets.getAsset({ + url: context.memo.jsonPath, + responseType: 'json', + decompress: true, + }, function(error, result) { + expect(error).toMatch(/gunzip error/); + done(); + }); + }); + }); + describe('.setMapping', function() { it('-- invalid path', function(done) { Assets.setMapping('foo', NULL_HASH, function(error/*, result*/) { expect(error).toEqual('Path is invalid'); @@ -136,7 +412,7 @@ describe("Assets", function () { }); }); }); - describe('Asset.getMapping', function() { + describe('.getMapping', function() { it('-- invalid path throws immediate', function() { expect(function() { Assets.getMapping('foo', function(error, hash) { @@ -161,12 +437,57 @@ describe("Assets", function () { function run() {} function instrument_testrunner(force) { if (force || typeof describe === 'undefined') { + var oldPrint = print; + window = new OverlayWebWindow({ + title: 'assetUnitTests.js', + width: 640, + height: 480, + source: 'about:blank', + }); + Script.scriptEnding.connect(window, 'close'); + window.closed.connect(Script, 'stop'); + // wait for window (ready) and test runner (ran) both to initialize + var ready = false; + var ran = false; + window.webEventReceived.connect(function() { ready = true; maybeRun(); }); + run = function() { ran = true; maybeRun(); }; + + window.setURL([ + 'data:text/html;text,', + '', + '
', + '', + '', + ].join('\n')); + print = function() { + var str = [].slice.call(arguments).join(' '); + if (ready) { + window.emitScriptEvent(str + '\n'); + } else { + oldPrint('!ready', str); + } + }; + // Include testing library Script.include('../../libraries/jasmine/jasmine.js'); Script.include('../../libraries/jasmine/hifi-boot.js'); - run = function() { - if (!/:console/.test(Script.resolvePath(''))) { + function maybeRun() { + if (!ran || !ready) { + return oldPrint('doRun -- waiting for both script and web window to become available'); + } + if (!force) { // invoke Script.stop (after any async tests complete) jasmine.getEnv().addReporter({ jasmineDone: Script.stop }); } else { From 97889b30d5cc420065fa7459c7bcd4cbc869955e Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 23 Jan 2018 03:21:19 -0500 Subject: [PATCH 44/73] remove unnecessary lambda capture --- libraries/networking/src/AssetResourceRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 9523a9aa26..6d5bbb3ac5 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -146,7 +146,7 @@ void AssetResourceRequest::requestHash(const AssetUtils::AssetHash& hash) { _assetRequest = assetClient->createRequest(hash, _byteRange); connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress); - connect(_assetRequest, &AssetRequest::finished, this, [this, hash](AssetRequest* req) { + connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) { Q_ASSERT(_state == InProgress); Q_ASSERT(req == _assetRequest); Q_ASSERT(req->getState() == AssetRequest::Finished); From 16ca0eebed0962651fb1c8da3f9fced7c7b5661b Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 23 Jan 2018 03:36:00 -0500 Subject: [PATCH 45/73] fix atp-client AssetUtils:: namespace --- tools/atp-client/src/ATPClientApp.cpp | 2 +- tools/atp-client/src/ATPClientApp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp index 9fd1bf8d4f..307c913a96 100644 --- a/tools/atp-client/src/ATPClientApp.cpp +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -361,7 +361,7 @@ void ATPClientApp::lookupAsset() { request->start(); } -void ATPClientApp::download(AssetHash hash) { +void ATPClientApp::download(AssetUtils::AssetHash hash) { auto assetClient = DependencyManager::get(); auto assetRequest = new AssetRequest(hash); diff --git a/tools/atp-client/src/ATPClientApp.h b/tools/atp-client/src/ATPClientApp.h index a3904d6e50..42760ceca9 100644 --- a/tools/atp-client/src/ATPClientApp.h +++ b/tools/atp-client/src/ATPClientApp.h @@ -45,7 +45,7 @@ private: void setMapping(QString hash); void lookupAsset(); void listAssets(); - void download(AssetHash hash); + void download(AssetUtils::AssetHash hash); void finish(int exitCode); bool _verbose; From 86ce9766f3ff8b5d746c5b437c8084b8b2c8fc12 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 23 Jan 2018 10:10:09 -0800 Subject: [PATCH 46/73] Fix security image index clear after back nav --- .../qml/hifi/commerce/wallet/SecurityImageSelection.qml | 4 ++++ interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index 1f5e67eaa5..c6c6056605 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -99,6 +99,10 @@ Item { function initModel() { gridModel.initModel(); } + + function resetSelection() { + securityImageGrid.currentIndex = -1; + } // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 21da38def0..fd74b07465 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -348,6 +348,7 @@ Item { width: 200; text: "Back" onClicked: { + securityImageSelection.resetSelection(); root.activeView = "step_1"; } } @@ -516,6 +517,7 @@ Item { width: 200; text: "Back" onClicked: { + securityImageSelection.resetSelection(); root.lastPage = "step_3"; root.activeView = "step_2"; } From 76cbc4ee55ec7349b19647b8eaba654b62444f44 Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 23 Jan 2018 14:47:49 -0500 Subject: [PATCH 47/73] separate getAsset fail/then handlers --- libraries/script-engine/src/AssetScriptingInterface.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 7c9bf2c4eb..78d0b323ee 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -236,11 +236,12 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, Promise fetched = jsPromiseReady(makePromise("fetched"), scope, callback); Promise mapped = makePromise("mapped"); - mapped->ready([=](QString error, QVariantMap result) { + mapped->fail(fetched); + mapped->then([=](QVariantMap result) { QString hash = result.value("hash").toString(); QString url = result.value("url").toString(); - if (!error.isEmpty() || !AssetUtils::isValidHash(hash)) { - fetched->reject(error.isEmpty() ? "internal hash error: " + hash : error, result); + if (!AssetUtils::isValidHash(hash)) { + fetched->reject("internal hash error: " + hash, result); } else { Promise promise = loadAsset(hash, decompress, responseType); promise->mixin(result); From 9fb20bee805beb8016a8a0248283c4957cd97bf8 Mon Sep 17 00:00:00 2001 From: humbletim Date: Tue, 23 Jan 2018 15:13:53 -0500 Subject: [PATCH 48/73] remove extra space --- libraries/entities/src/TextEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 72a7b56eea..7b1089e6ed 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -131,7 +131,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { + QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); From 1920bedaf3342d47f9da5fece8c44557b8852699 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 23 Jan 2018 13:41:15 -0800 Subject: [PATCH 49/73] Fix the text filter in My Purchases --- .../commerce/common/SortableListModel.qml | 58 ++++++++++++++----- .../qml/hifi/commerce/purchases/Purchases.qml | 2 +- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/SortableListModel.qml b/interface/resources/qml/hifi/commerce/common/SortableListModel.qml index 2d82e42ddb..0951d25950 100644 --- a/interface/resources/qml/hifi/commerce/common/SortableListModel.qml +++ b/interface/resources/qml/hifi/commerce/common/SortableListModel.qml @@ -17,6 +17,7 @@ ListModel { id: root; property string sortColumnName: ""; property bool isSortingDescending: true; + property bool valuesAreNumerical: false; function swap(a, b) { if (a < b) { @@ -29,26 +30,51 @@ ListModel { } function partition(begin, end, pivot) { - var piv = get(pivot)[sortColumnName]; - swap(pivot, end - 1); - var store = begin; + if (valuesAreNumerical) { + var piv = get(pivot)[sortColumnName]; + swap(pivot, end - 1); + var store = begin; - for (var i = begin; i < end - 1; ++i) { - if (isSortingDescending) { - if (get(i)[sortColumnName] < piv) { - swap(store, i); - ++store; - } - } else { - if (get(i)[sortColumnName] > piv) { - swap(store, i); - ++store; + for (var i = begin; i < end - 1; ++i) { + var currentElement = get(i)[sortColumnName]; + if (isSortingDescending) { + if (currentElement < piv) { + swap(store, i); + ++store; + } + } else { + if (currentElement > piv) { + swap(store, i); + ++store; + } } } - } - swap(end - 1, store); + swap(end - 1, store); - return store; + return store; + } else { + var piv = get(pivot)[sortColumnName].toLowerCase(); + swap(pivot, end - 1); + var store = begin; + + for (var i = begin; i < end - 1; ++i) { + var currentElement = get(i)[sortColumnName].toLowerCase(); + if (isSortingDescending) { + if (currentElement < piv) { + swap(store, i); + ++store; + } + } else { + if (currentElement > piv) { + swap(store, i); + ++store; + } + } + } + swap(end - 1, store); + + return store; + } } function qsort(begin, end) { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 87b784bc4e..a9961dc17e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -677,7 +677,7 @@ Rectangle { } } - if (sameItemCount !== tempPurchasesModel.count) { + if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== "") { filteredPurchasesModel.clear(); for (var i = 0; i < tempPurchasesModel.count; i++) { filteredPurchasesModel.append(tempPurchasesModel.get(i)); From 991ba7f1951db5043dd8fbf73ba6403b193f38eb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 23 Jan 2018 16:05:43 -0800 Subject: [PATCH 50/73] Bug fix for twisted knees on some avatars. The FBXReader inverse bind pose calculation can sometimes introduce floating point fuzz into the bottom row of the matrix. The Transform class checks this bottom row before doing decomposition into translation, rotation and scale. If it detects that this row is not exactly (0, 0, 0, 1) it aborts. And returns identity. To guarantee that it preforms the decomposition correctly slam the row to (0, 0, 0, 1), before conversion to a Transform instance. --- libraries/fbx/src/FBXReader.cpp | 14 ++++++++++++-- libraries/render-utils/src/Skinning.slh | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 4bc5eb1b6d..171fc88443 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1733,8 +1733,18 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS qCDebug(modelformat) << "Joint not in model list: " << jointID; fbxCluster.jointIndex = 0; } + fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + + // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and + // sometimes floating point fuzz can be introduced after the inverse. + fbxCluster.inverseBindMatrix[0][3] = 0.0f; + fbxCluster.inverseBindMatrix[1][3] = 0.0f; + fbxCluster.inverseBindMatrix[2][3] = 0.0f; + fbxCluster.inverseBindMatrix[3][3] = 1.0f; + fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix); + extracted.mesh.clusters.append(fbxCluster); // override the bind rotation with the transform link @@ -1836,13 +1846,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } // now that we've accumulated the most relevant weights for each vertex - // normalize and compress to 8-bits + // normalize and compress to 16-bits extracted.mesh.clusterWeights.fill(0, numClusterIndices); int numVertices = extracted.mesh.vertices.size(); for (int i = 0; i < numVertices; ++i) { int j = i * WEIGHTS_PER_VERTEX; - // normalize weights into uint8_t + // normalize weights into uint16_t float totalWeight = weightAccumulators[j]; for (int k = j + 1; k < j + WEIGHTS_PER_VERTEX; ++k) { totalWeight += weightAccumulators[k]; diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 49d0df3d2c..e85ee75c58 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -39,12 +39,12 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) { twoRealXZ - twoRealYW, 0.0); vec4 col1 = vec4(twoRealXY - twoRealZW, - 1 - twoRealXSq - twoRealZSq, + 1.0 - twoRealXSq - twoRealZSq, twoRealYZ + twoRealXW, 0.0); vec4 col2 = vec4(twoRealXZ + twoRealYW, twoRealYZ - twoRealXW, - 1 - twoRealXSq - twoRealYSq, + 1.0 - twoRealXSq - twoRealYSq, 0.0); vec4 col3 = vec4(2.0 * (-dual.w * real.x + dual.x * real.w - dual.y * real.z + dual.z * real.y), 2.0 * (-dual.w * real.y + dual.x * real.z + dual.y * real.w - dual.z * real.x), From 6bb325f6a2d82006b3d82a4c22fdac16309f337c Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 23 Jan 2018 17:25:02 -0800 Subject: [PATCH 51/73] Implemented new recursive functionality. --- tools/auto-tester/src/Test.cpp | 88 ++++------------------------------ tools/auto-tester/src/Test.h | 2 +- 2 files changed, 10 insertions(+), 80 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 6c637ab404..a1662730c8 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -306,8 +306,8 @@ void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progress zipAndDeleteTestResultsFolder(); } -void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { - textStream << "var test" << testNumber << " = Script.require(\"" << "file:///" << testPathname + "\");" << endl; +void Test::importTest(QTextStream& textStream, const QString& testPathname) { + textStream << "Script.include(\"" << "file:///" << testPathname + "\");" << endl; } // Creates a single script in a user-selected folder. @@ -330,12 +330,14 @@ void Test::createRecursiveScript() { } QTextStream textStream(&allTestsFilename); - textStream << "// This is an automatically generated file, created by auto-tester" << endl; + textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl; + + textStream << "var autoTester = Script.require(\"https://github.com/highfidelity/hifi_tests/blob/master/tests/utils/autoTester.js\");" << endl; + textStream << "autoTester.enableRecursive();" << endl << endl; // The main will call each test after the previous test is completed // This is implemented with an interval timer that periodically tests if a // running test has increment a testNumber variable that it received as an input. - int testNumber = 1; QVector testPathnames; // First test if top-level folder has a test.js file @@ -343,8 +345,7 @@ void Test::createRecursiveScript() { QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test - importTest(textStream, testPathname, testNumber); - ++testNumber; + importTest(textStream, testPathname); testPathnames << testPathname; } @@ -363,8 +364,7 @@ void Test::createRecursiveScript() { QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test - importTest(textStream, testPathname, testNumber); - ++testNumber; + importTest(textStream, testPathname); testPathnames << testPathname; } @@ -377,79 +377,9 @@ void Test::createRecursiveScript() { } 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("auto"); - // 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 << "(\"auto\");" << 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; + textStream << "autoTester.runRecursive();" << endl; allTestsFilename.close(); - messageBox.information(0, "Success", "Script has been created"); } diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index aa1346fa2a..3177df4d47 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -36,7 +36,7 @@ public: bool isInSnapshotFilenameFormat(QString filename); bool isInExpectedImageFilenameFormat(QString filename); - void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); + void importTest(QTextStream& textStream, const QString& testPathname); void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); From f9e0c724ce9962b74d78046747596300482ae206 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 23 Jan 2018 18:09:45 -0800 Subject: [PATCH 52/73] Added '?raw=true' suffices where needed. --- tools/auto-tester/src/Test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index a1662730c8..816eac7fbd 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -307,7 +307,7 @@ void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progress } void Test::importTest(QTextStream& textStream, const QString& testPathname) { - textStream << "Script.include(\"" << "file:///" << testPathname + "\");" << endl; + textStream << "Script.include(\"" << "file:///" << testPathname + "?raw=true\");" << endl; } // Creates a single script in a user-selected folder. @@ -332,7 +332,7 @@ void Test::createRecursiveScript() { QTextStream textStream(&allTestsFilename); textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl; - textStream << "var autoTester = Script.require(\"https://github.com/highfidelity/hifi_tests/blob/master/tests/utils/autoTester.js\");" << endl; + textStream << "var autoTester = Script.require(\"https://github.com/highfidelity/hifi_tests/blob/master/tests/utils/autoTester.js?raw=true\");" << endl; textStream << "autoTester.enableRecursive();" << endl << endl; // The main will call each test after the previous test is completed From 64a55f139383644dfb81514e0ed4d74b5d745569 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 23 Jan 2018 20:36:11 -0800 Subject: [PATCH 53/73] fix asan build --- tests/gl/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/gl/CMakeLists.txt b/tests/gl/CMakeLists.txt index 2b2b79d8b2..40bb64be1c 100644 --- a/tests/gl/CMakeLists.txt +++ b/tests/gl/CMakeLists.txt @@ -1,6 +1,7 @@ set(TARGET_NAME gl-test) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL) +setup_memory_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(shared gl) package_libraries_for_deployment() From c2ee13931ec7d01dd1417fbf1e08d06ca2b526e5 Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 24 Jan 2018 13:38:10 -0500 Subject: [PATCH 54/73] changes per CR feedback; additional jsdoc --- libraries/networking/src/AssetClient.cpp | 2 +- .../src/BaseAssetScriptingInterface.h | 3 --- .../src/AssetScriptingInterface.cpp | 20 ++++++++++++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index a0c86a25e8..4d987a3200 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -92,7 +92,7 @@ MiniPromise::Promise AssetClient::cacheInfoRequestAsync(MiniPromise::Promise def if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "cacheInfoRequestAsync", Q_ARG(MiniPromise::Promise, deferred)); } else { - auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache()); + auto cache = qobject_cast(NetworkAccessManager::getInstance().cache()); if (cache) { deferred->resolve({ { "cacheDirectory", cache->cacheDirectory() }, diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index 5ac391a4d7..a51f4a5105 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -21,9 +21,6 @@ #include "NetworkAccessManager.h" #include -/**jsdoc - * @namespace Assets - */ class BaseAssetScriptingInterface : public QObject { Q_OBJECT public: diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 78d0b323ee..1c811573fb 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -215,6 +215,21 @@ void AssetScriptingInterface::deleteAsset(QScriptValue options, QScriptValue sco jsVerify(false, "TODO: deleteAsset API"); } +/**jsdoc + * @typedef {string} Assets.GetOptions.ResponseType + *

Available responseType values for use with @{link Assets.getAsset} and @{link Assets.loadFromCache} configuration option.

+ * + * + * + * + * + * + * + * + * + *
responseTypetypeof response value
"text"contents returned as utf-8 decoded String value
"arraybuffer"contents as a binary ArrayBuffer object
"json"contents as a parsed JSON object
+ */ + void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback) { JS_VERIFY(options.isObject() || options.isString(), "expected request options Object or URL as first parameter"); @@ -224,7 +239,7 @@ void AssetScriptingInterface::getAsset(QScriptValue options, QScriptValue scope, if (options.isString()) { url = options.toString(); } - if (responseType.isEmpty() || responseType == "string") { + if (responseType.isEmpty()) { responseType = "text"; } auto asset = AssetUtils::getATPUrl(url).path(); @@ -389,8 +404,7 @@ bool AssetScriptingInterface::canWriteCacheValue(const QUrl& url) { // allow cache writes only from Client, EntityServer and Agent scripts bool isAllowedContext = ( scriptEngine->isClientScript() || - scriptEngine->isAgentScript() || - scriptEngine->isEntityServerScript() + scriptEngine->isAgentScript() ); if (!isAllowedContext) { qCDebug(scriptengine) << __FUNCTION__ << "invalid context" << scriptEngine->getContext() << url; From 63a226f9994a025093ef9043603d3a84dc410b17 Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 24 Jan 2018 13:49:58 -0500 Subject: [PATCH 55/73] make fromScriptValue logic easier to follow for CR; eliminate extra QByteArray conversion --- .../script-engine/src/ArrayBufferClass.cpp | 33 ++++++++++++++--- .../script-engine/src/ArrayBufferClass.h | 2 -- .../src/ArrayBufferViewClass.cpp | 35 +------------------ .../script-engine/src/ArrayBufferViewClass.h | 4 --- 4 files changed, 29 insertions(+), 45 deletions(-) diff --git a/libraries/script-engine/src/ArrayBufferClass.cpp b/libraries/script-engine/src/ArrayBufferClass.cpp index b84188f707..4a06dee391 100644 --- a/libraries/script-engine/src/ArrayBufferClass.cpp +++ b/libraries/script-engine/src/ArrayBufferClass.cpp @@ -18,14 +18,19 @@ #include "ArrayBufferClass.h" + static const QString CLASS_NAME = "ArrayBuffer"; +// FIXME: Q_DECLARE_METATYPE is global and really belongs in a shared header file, not per .cpp like this +// (see DataViewClass.cpp, etc. which would also have to be updated to resolve) +Q_DECLARE_METATYPE(QScriptClass*) Q_DECLARE_METATYPE(QByteArray*) +int qScriptClassPointerMetaTypeId = qRegisterMetaType(); +int qByteArrayPointerMetaTypeId = qRegisterMetaType(); ArrayBufferClass::ArrayBufferClass(ScriptEngine* scriptEngine) : QObject(scriptEngine), -QScriptClass(scriptEngine), -_scriptEngine(scriptEngine) { +QScriptClass(scriptEngine) { qScriptRegisterMetaType(engine(), toScriptValue, fromScriptValue); QScriptValue global = engine()->globalObject(); @@ -144,12 +149,30 @@ QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteA QScriptValue ctor = engine->globalObject().property(CLASS_NAME); ArrayBufferClass* cls = qscriptvalue_cast(ctor.data()); if (!cls) { - return engine->newVariant(QVariant::fromValue(ba)); + if (engine->currentContext()) { + engine->currentContext()->throwError("arrayBufferClass::toScriptValue -- could not get " + CLASS_NAME + " class constructor"); + } + return QScriptValue::NullValue; } return cls->newInstance(ba); } -void ArrayBufferClass::fromScriptValue(const QScriptValue& obj, QByteArray& ba) { - ba = qvariant_cast(obj.data().toVariant()); +void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& byteArray) { + if (object.isString()) { + // UTF-8 encoded String + byteArray = object.toString().toUtf8(); + } else if (object.isArray()) { + // Array of uint8s eg: [ 128, 3, 25, 234 ] + auto Uint8Array = object.engine()->globalObject().property("Uint8Array"); + auto typedArray = Uint8Array.construct(QScriptValueList{object}); + if (QByteArray* buffer = qscriptvalue_cast(typedArray.property("buffer"))) { + byteArray = *buffer; + } + } else if (object.isObject()) { + // ArrayBuffer instance (or any JS class that supports coercion into QByteArray*) + if (QByteArray* buffer = qscriptvalue_cast(object.data())) { + byteArray = *buffer; + } + } } diff --git a/libraries/script-engine/src/ArrayBufferClass.h b/libraries/script-engine/src/ArrayBufferClass.h index 69c2cc0799..a2cf08cc06 100644 --- a/libraries/script-engine/src/ArrayBufferClass.h +++ b/libraries/script-engine/src/ArrayBufferClass.h @@ -40,7 +40,6 @@ public: QString name() const override; QScriptValue prototype() const override; - ScriptEngine* getEngine() { return _scriptEngine; } private: static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); @@ -55,7 +54,6 @@ private: QScriptString _name; QScriptString _byteLength; - ScriptEngine* _scriptEngine; }; #endif // hifi_ArrayBufferClass_h diff --git a/libraries/script-engine/src/ArrayBufferViewClass.cpp b/libraries/script-engine/src/ArrayBufferViewClass.cpp index 84cb32d665..cf776ed834 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.cpp +++ b/libraries/script-engine/src/ArrayBufferViewClass.cpp @@ -11,8 +11,7 @@ #include "ArrayBufferViewClass.h" -int qScriptClassPointerMetaTypeId = qRegisterMetaType(); -int qByteArrayMetaTypeId = qRegisterMetaType(); +Q_DECLARE_METATYPE(QByteArray*) ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngine* scriptEngine) : QObject(scriptEngine), @@ -22,7 +21,6 @@ _scriptEngine(scriptEngine) { _bufferName = engine()->toStringHandle(BUFFER_PROPERTY_NAME.toLatin1()); _byteOffsetName = engine()->toStringHandle(BYTE_OFFSET_PROPERTY_NAME.toLatin1()); _byteLengthName = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); - registerMetaTypes(scriptEngine); } QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& object, @@ -52,34 +50,3 @@ QScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const QScriptVal const QScriptString& name, uint id) { return QScriptValue::Undeletable; } - -namespace { - void byteArrayFromScriptValue(const QScriptValue& object, QByteArray& byteArray) { - if (object.isValid()) { - if (object.isObject()) { - if (object.isArray()) { - auto Uint8Array = object.engine()->globalObject().property("Uint8Array"); - auto typedArray = Uint8Array.construct(QScriptValueList{object}); - byteArray = qvariant_cast(typedArray.property("buffer").toVariant()); - } else { - byteArray = qvariant_cast(object.data().toVariant()); - } - } else { - byteArray = object.toString().toUtf8(); - } - } - } - - QScriptValue byteArrayToScriptValue(QScriptEngine *engine, const QByteArray& byteArray) { - QScriptValue data = engine->newVariant(QVariant::fromValue(byteArray)); - QScriptValue constructor = engine->globalObject().property("ArrayBuffer"); - Q_ASSERT(constructor.isValid()); - auto array = qscriptvalue_cast(constructor.data()); - Q_ASSERT(array); - return engine->newObject(array, data); - } -} - -void ArrayBufferViewClass::registerMetaTypes(QScriptEngine* scriptEngine) { - qScriptRegisterMetaType(scriptEngine, byteArrayToScriptValue, byteArrayFromScriptValue); -} diff --git a/libraries/script-engine/src/ArrayBufferViewClass.h b/libraries/script-engine/src/ArrayBufferViewClass.h index 038cc75ffd..67af4a3fc3 100644 --- a/libraries/script-engine/src/ArrayBufferViewClass.h +++ b/libraries/script-engine/src/ArrayBufferViewClass.h @@ -29,7 +29,6 @@ static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; class ArrayBufferViewClass : public QObject, public QScriptClass { Q_OBJECT public: - static void registerMetaTypes(QScriptEngine* scriptEngine); ArrayBufferViewClass(ScriptEngine* scriptEngine); ScriptEngine* getScriptEngine() { return _scriptEngine; } @@ -50,7 +49,4 @@ protected: ScriptEngine* _scriptEngine; }; -Q_DECLARE_METATYPE(QScriptClass*) -Q_DECLARE_METATYPE(QByteArray) - #endif // hifi_ArrayBufferViewClass_h From 3ebf322fcf994ff866e3e8d368393daab7156518 Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 24 Jan 2018 14:13:34 -0500 Subject: [PATCH 56/73] switch to foreach per CR feeddback --- libraries/networking/src/AssetClient.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 4d987a3200..1db93e8cb9 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -121,10 +121,9 @@ MiniPromise::Promise AssetClient::queryCacheMetaAsync(const QUrl& url, MiniPromi { "metaDataURL", metaData.url() }, }); } else { - QHashIterator i(metaData.attributes()); - while (i.hasNext()) { - i.next(); - attributes[QString::number(i.key())] = i.value(); + auto metaAttributes = metaData.attributes(); + foreach(QNetworkRequest::Attribute k, metaAttributes.keys()) { + attributes[QString::number(k)] = metaAttributes[k]; } for (const auto& i : metaData.rawHeaders()) { rawHeaders[i.first] = i.second; From e8f0df43638ca5cee51f6558c781d4031e8f799b Mon Sep 17 00:00:00 2001 From: humbletim Date: Wed, 24 Jan 2018 14:28:22 -0500 Subject: [PATCH 57/73] switch to bool per CR feedback --- libraries/networking/src/BaseAssetScriptingInterface.cpp | 4 ++-- libraries/networking/src/BaseAssetScriptingInterface.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index d62e992822..3149bbc768 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -42,7 +42,7 @@ bool BaseAssetScriptingInterface::initializeCache() { if (!assetClient()) { return false; // not yet possible to initialize the cache } - if (!_cacheDirectory.isEmpty()) { + if (_cacheReady) { return true; // cache is ready } @@ -51,7 +51,7 @@ bool BaseAssetScriptingInterface::initializeCache() { Promise deferred = makePromise("BaseAssetScriptingInterface--queryCacheStatus"); deferred->then([this](QVariantMap result) { - _cacheDirectory = result.value("cacheDirectory").toString(); + _cacheReady = !result.value("cacheDirectory").toString().isEmpty(); }); deferred->fail([](QString error) { qDebug() << "BaseAssetScriptingInterface::queryCacheStatus ERROR" << QThread::currentThread() << error; diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index a51f4a5105..336f3f81db 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -40,7 +40,7 @@ public slots: QString hashDataHex(const QByteArray& data) { return hashData(data).toHex(); } protected: - QString _cacheDirectory; + bool _cacheReady{ false }; bool initializeCache(); Promise getCacheStatus(); Promise queryCacheMeta(const QUrl& url); From 94e1c425ab618bf8b8105214d7928c38e029c527 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 24 Jan 2018 17:03:16 -0800 Subject: [PATCH 58/73] Fix MacOS Security Pic visibility; fix incorrect button state when changing pic --- .../resources/qml/hifi/commerce/wallet/Security.qml | 2 +- .../hifi/commerce/wallet/SecurityImageChange.qml | 13 ++----------- .../hifi/commerce/wallet/SecurityImageSelection.qml | 3 ++- interface/src/commerce/Wallet.cpp | 4 ++-- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index d825196655..634a68d117 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -193,7 +193,7 @@ Item { color: hifi.colors.white; } - // "Change Passphrase" button + // "Change Security Pic" button HifiControlsUit.Button { id: changeSecurityImageButton; color: hifi.buttons.blue; diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index 2ad2b75553..86a4220b74 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -34,13 +34,11 @@ Item { securityImageChangePageSecurityImage.source = "image://security/securityImage"; if (exists) { // Success submitting new security image if (root.justSubmitted) { - root.resetSubmitButton(); sendSignalToWallet({method: "walletSecurity_changeSecurityImageSuccess"}); root.justSubmitted = false; } } else if (root.justSubmitted) { // Error submitting new security image. - root.resetSubmitButton(); root.justSubmitted = false; } } @@ -180,7 +178,8 @@ Item { // "Submit" button HifiControlsUit.Button { id: securityImageSubmitButton; - enabled: securityImageSelection.currentIndex !== -1; + text: root.justSubmitted ? "Submitting..." : "Submit"; + enabled: securityImageSelection.currentIndex !== -1 && !root.justSubmitted; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.dark; anchors.top: parent.top; @@ -188,11 +187,8 @@ Item { anchors.right: parent.right; anchors.rightMargin: 20; width: 150; - text: "Submit"; onClicked: { root.justSubmitted = true; - securityImageSubmitButton.text = "Submitting..."; - securityImageSubmitButton.enabled = false; var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) Commerce.chooseSecurityImage(securityImagePath); } @@ -205,11 +201,6 @@ Item { signal sendSignalToWallet(var msg); - function resetSubmitButton() { - securityImageSubmitButton.enabled = true; - securityImageSubmitButton.text = "Submit"; - } - function initModel() { securityImageSelection.initModel(); } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index c6c6056605..56b78c5865 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -24,7 +24,7 @@ Item { HifiConstants { id: hifi; } id: root; - property int currentIndex: securityImageGrid.currentIndex; + property alias currentIndex: securityImageGrid.currentIndex; // This will cause a bug -- if you bring up security image selection in HUD mode while // in HMD while having HMD preview enabled, then move, then finish passphrase selection, @@ -98,6 +98,7 @@ Item { function initModel() { gridModel.initModel(); + securityImageGrid.currentIndex = -1; } function resetSelection() { diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 8b73042ada..9599af827f 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -591,8 +591,8 @@ void Wallet::chooseSecurityImage(const QString& filename) { if (_securityImage) { delete _securityImage; } - QString path = qApp->applicationDirPath(); - path.append("/resources/qml/hifi/commerce/wallet/"); + QString path = PathUtils::resourcesPath(); + path.append("/qml/hifi/commerce/wallet/"); path.append(filename); // now create a new security image pixmap From a252e90f969e85503f2a83380a6e101026732783 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 24 Jan 2018 18:58:08 -0800 Subject: [PATCH 59/73] Bug fix for incorrect positioned eyes on other peoples avatars There was a bug in writeBitVector(), where the last byte was not consistantly written into the destination buffer. A unit test was added to verify that writeBitVector() and readBitVector() are correct. --- libraries/shared/src/BitVectorHelpers.h | 16 +++-- tests/shared/src/BitVectorHelperTests.cpp | 80 +++++++++++++++++++++++ tests/shared/src/BitVectorHelperTests.h | 23 +++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 tests/shared/src/BitVectorHelperTests.cpp create mode 100644 tests/shared/src/BitVectorHelperTests.h diff --git a/libraries/shared/src/BitVectorHelpers.h b/libraries/shared/src/BitVectorHelpers.h index 71826036eb..9f5af0c380 100644 --- a/libraries/shared/src/BitVectorHelpers.h +++ b/libraries/shared/src/BitVectorHelpers.h @@ -12,14 +12,14 @@ #ifndef hifi_BitVectorHelpers_h #define hifi_BitVectorHelpers_h -size_t calcBitVectorSize(int numBits) { +int calcBitVectorSize(int numBits) { return ((numBits - 1) >> 3) + 1; } // func should be of type bool func(int index) template -size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { - size_t totalBytes = ((numBits - 1) >> 3) + 1; +int writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { + int totalBytes = calcBitVectorSize(numBits); uint8_t* cursor = destinationBuffer; uint8_t byte = 0; uint8_t bit = 0; @@ -34,13 +34,19 @@ size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { bit = 0; } } + // write the last byte, if necessary + if (bit != 0) { + *cursor++ = byte; + } + + assert((int)(cursor - destinationBuffer) == totalBytes); return totalBytes; } // func should be of type 'void func(int index, bool value)' template -size_t readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) { - size_t totalBytes = ((numBits - 1) >> 3) + 1; +int readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) { + int totalBytes = calcBitVectorSize(numBits); const uint8_t* cursor = sourceBuffer; uint8_t bit = 0; diff --git a/tests/shared/src/BitVectorHelperTests.cpp b/tests/shared/src/BitVectorHelperTests.cpp new file mode 100644 index 0000000000..070e90eec7 --- /dev/null +++ b/tests/shared/src/BitVectorHelperTests.cpp @@ -0,0 +1,80 @@ +// +// BitVectorHelperTests.cpp +// tests/shared/src +// +// Copyright 2018 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 "BitVectorHelperTests.h" + +#include + +#include "../QTestExtensions.h" +#include +#include +#include +#include + +QTEST_MAIN(BitVectorHelperTests) + +const int BITS_IN_BYTE = 8; + +void BitVectorHelperTests::sizeTest() { + std::vector sizes = {0, 6, 7, 8, 30, 31, 32, 33, 87, 88, 89, 90, 90, 91, 92, 93}; + for (auto& size : sizes) { + const int oldWay = (int)ceil((float)size / (float)BITS_IN_BYTE); + const int newWay = (int)calcBitVectorSize(size); + QCOMPARE(oldWay, newWay); + } +} + +static void readWriteHelper(const std::vector& src) { + + int numBits = (int)src.size(); + int numBytes = calcBitVectorSize(numBits); + uint8_t* bytes = new uint8_t[numBytes]; + memset(bytes, numBytes, sizeof(uint8_t)); + int numBytesWritten = writeBitVector(bytes, numBits, [&](int i) { + return src[i]; + }); + QCOMPARE(numBytesWritten, numBytes); + + std::vector dst; + int numBytesRead = readBitVector(bytes, numBits, [&](int i, bool value) { + dst.push_back(value); + }); + QCOMPARE(numBytesRead, numBytes); + + QCOMPARE(numBits, (int)src.size()); + QCOMPARE(numBits, (int)dst.size()); + for (int i = 0; i < numBits; i++) { + bool a = src[i]; + bool b = dst[i]; + QCOMPARE(a, b); + } +} + +void BitVectorHelperTests::readWriteTest() { + std::vector sizes = {0, 6, 7, 8, 30, 31, 32, 33, 87, 88, 89, 90, 90, 91, 92, 93}; + + for (auto& size : sizes) { + std::vector allTrue(size, true); + std::vector allFalse(size, false); + std::vector evenSet; + evenSet.reserve(size); + std::vector oddSet; + oddSet.reserve(size); + for (int i = 0; i < size; i++) { + bool isOdd = (i & 0x1) > 0; + evenSet.push_back(!isOdd); + oddSet.push_back(isOdd); + } + readWriteHelper(allTrue); + readWriteHelper(allFalse); + readWriteHelper(evenSet); + readWriteHelper(oddSet); + } +} diff --git a/tests/shared/src/BitVectorHelperTests.h b/tests/shared/src/BitVectorHelperTests.h new file mode 100644 index 0000000000..1f52ba1ac9 --- /dev/null +++ b/tests/shared/src/BitVectorHelperTests.h @@ -0,0 +1,23 @@ +// +// BitVectorHelperTests.h +// tests/shared/src +// +// Copyright 2018 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_BitVectorHelperTests_h +#define hifi_BitVectorHelperTests_h + +#include + +class BitVectorHelperTests : public QObject { + Q_OBJECT +private slots: + void sizeTest(); + void readWriteTest(); +}; + +#endif // hifi_BitVectorHelperTests_h From 3915a2d4ba12249d74f9d39e738703ce9c0fa31b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Jan 2018 11:07:39 -0800 Subject: [PATCH 60/73] Fix resource loading from source tree --- libraries/shared/src/PathUtils.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 35d7554f39..41db7281ac 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -29,7 +29,6 @@ #include #endif - #include "shared/GlobalAppProperties.h" #include "SharedUtil.h" @@ -41,8 +40,15 @@ QString TEMP_DIR_FORMAT { "%1-%2-%3" }; #if defined(Q_OS_OSX) static bool USE_SOURCE_TREE_RESOURCES = true; #else -static const QString USE_SOURCE_TREE_RESOURCES_FLAG("HIFI_USE_SOURCE_TREE_RESOURCES"); -static bool USE_SOURCE_TREE_RESOURCES = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG); +static bool USE_SOURCE_TREE_RESOURCES() { + static bool result = false; + static std::once_flag once; + std::call_once(once, [&] { + const QString USE_SOURCE_TREE_RESOURCES_FLAG("HIFI_USE_SOURCE_TREE_RESOURCES"); + result = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG); + }); + return result; +} #endif #endif @@ -77,7 +83,7 @@ const QString& PathUtils::resourcesPath() { #endif #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) - if (USE_SOURCE_TREE_RESOURCES) { + if (USE_SOURCE_TREE_RESOURCES()) { // For dev builds, optionally load content from the Git source tree staticResourcePath = projectRootPath() + "/interface/resources/"; } @@ -100,7 +106,7 @@ const QString& PathUtils::resourcesUrl() { #endif #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) - if (USE_SOURCE_TREE_RESOURCES) { + if (USE_SOURCE_TREE_RESOURCES()) { // For dev builds, optionally load content from the Git source tree staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/").toString(); } From afc71a5ac5090487961273f678a02b7363555cf8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 25 Jan 2018 12:17:34 -0800 Subject: [PATCH 61/73] Fix Purchases sorting (again); Fix (i) icon loading --- .../hifi/commerce/common/SortableListModel.qml | 16 +++++++++------- .../qml/hifi/commerce/purchases/Purchases.qml | 8 ++++++-- .../src/ui/overlays/ContextOverlayInterface.cpp | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/SortableListModel.qml b/interface/resources/qml/hifi/commerce/common/SortableListModel.qml index 0951d25950..cfdd4abe04 100644 --- a/interface/resources/qml/hifi/commerce/common/SortableListModel.qml +++ b/interface/resources/qml/hifi/commerce/common/SortableListModel.qml @@ -22,7 +22,7 @@ ListModel { function swap(a, b) { if (a < b) { move(a, b, 1); - move (b - 1, a, 1); + move(b - 1, a, 1); } else if (a > b) { move(b, a, 1); move(a - 1, b, 1); @@ -34,16 +34,17 @@ ListModel { var piv = get(pivot)[sortColumnName]; swap(pivot, end - 1); var store = begin; + var i; - for (var i = begin; i < end - 1; ++i) { + for (i = begin; i < end - 1; ++i) { var currentElement = get(i)[sortColumnName]; if (isSortingDescending) { - if (currentElement < piv) { + if (currentElement > piv) { swap(store, i); ++store; } } else { - if (currentElement > piv) { + if (currentElement < piv) { swap(store, i); ++store; } @@ -56,16 +57,17 @@ ListModel { var piv = get(pivot)[sortColumnName].toLowerCase(); swap(pivot, end - 1); var store = begin; + var i; - for (var i = begin; i < end - 1; ++i) { + for (i = begin; i < end - 1; ++i) { var currentElement = get(i)[sortColumnName].toLowerCase(); if (isSortingDescending) { - if (currentElement < piv) { + if (currentElement > piv) { swap(store, i); ++store; } } else { - if (currentElement > piv) { + if (currentElement < piv) { swap(store, i); ++store; } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index a9961dc17e..2743677683 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -317,6 +317,7 @@ Rectangle { HifiControlsUit.TextField { id: filterBar; + property string previousText: ""; colorScheme: hifi.colorSchemes.faintGray; hasClearButton: true; hasRoundedBorder: true; @@ -329,6 +330,8 @@ Rectangle { onTextChanged: { buildFilteredPurchasesModel(); + purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + filterBar.previousText = filterBar.text; } onAccepted: { @@ -647,7 +650,8 @@ Rectangle { function sortByDate() { filteredPurchasesModel.sortColumnName = "purchase_date"; - filteredPurchasesModel.isSortingDescending = false; + filteredPurchasesModel.isSortingDescending = true; + filteredPurchasesModel.valuesAreNumerical = true; filteredPurchasesModel.quickSort(); } @@ -677,7 +681,7 @@ Rectangle { } } - if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== "") { + if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) { filteredPurchasesModel.clear(); for (var i = 0; i < tempPurchasesModel.count; i++) { filteredPurchasesModel.append(tempPurchasesModel.get(i)); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index d690880f99..58a77883d8 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -168,8 +168,8 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); _contextOverlay->setIgnoreRayIntersection(false); _contextOverlay->setDrawInFront(true); - _contextOverlay->setURL(PathUtils::resourcesPath() + "images/inspect-icon.png"); _contextOverlay->setIsFacingAvatar(true); + _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); _contextOverlayID = qApp->getOverlays().addOverlay(_contextOverlay); } _contextOverlay->setWorldPosition(contextOverlayPosition); From bde5282541cbd039183758521baf080cee509dba Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Jan 2018 12:43:49 -0800 Subject: [PATCH 62/73] Fix loading of the render config JSON --- libraries/render/src/render/Engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render/src/render/Engine.cpp b/libraries/render/src/render/Engine.cpp index 5f67d40d17..463b45451b 100644 --- a/libraries/render/src/render/Engine.cpp +++ b/libraries/render/src/render/Engine.cpp @@ -45,8 +45,8 @@ void Engine::load() { auto config = getConfiguration(); const QString configFile= "config/render.json"; - QUrl path(PathUtils::resourcesPath() + configFile); - QFile file(path.toString()); + QString path(PathUtils::resourcesPath() + configFile); + QFile file(path); if (!file.exists()) { qWarning() << "Engine configuration file" << path << "does not exist"; } else if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { From ac94327ec72edec08cdc5b188fd5d3028d574a3d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Jan 2018 12:44:55 -0800 Subject: [PATCH 63/73] Fix loading of the context overlay information icon --- interface/src/ui/overlays/ContextOverlayInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index d690880f99..d4138941ae 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -168,7 +168,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); _contextOverlay->setIgnoreRayIntersection(false); _contextOverlay->setDrawInFront(true); - _contextOverlay->setURL(PathUtils::resourcesPath() + "images/inspect-icon.png"); + _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); _contextOverlay->setIsFacingAvatar(true); _contextOverlayID = qApp->getOverlays().addOverlay(_contextOverlay); } From d4aef1ea8803c1e4213f96741b7efae060f746ab Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Jan 2018 13:03:42 -0800 Subject: [PATCH 64/73] Fix URLs in QSS files to use QRC base path --- interface/resources/styles/global.qss | 10 +++++----- interface/resources/styles/import_dialog.qss | 6 +++--- interface/resources/styles/log_dialog.qss | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/interface/resources/styles/global.qss b/interface/resources/styles/global.qss index 2554f3b2c9..778e5759b3 100644 --- a/interface/resources/styles/global.qss +++ b/interface/resources/styles/global.qss @@ -41,14 +41,14 @@ QSpinBox, QDoubleSpinBox { QDoubleSpinBox::up-arrow, QSpinBox::up-arrow { - background-image: url(styles/up.svg); + background-image: url(:/styles/up.svg); background-repeat: no-repeat; background-position: center center; } QDoubleSpinBox::down-arrow, QSpinBox::down-arrow { - background-image: url(styles/down.svg); + background-image: url(:/styles/down.svg); background-repeat: no-repeat; background-position: center center; } @@ -88,7 +88,7 @@ QSlider { QSlider::groove:horizontal { border: none; - background-image: url(styles/slider-bg.svg); + background-image: url(:/styles/slider-bg.svg); background-repeat: no-repeat; background-position: center center; } @@ -96,7 +96,7 @@ QSlider::groove:horizontal { QSlider::handle:horizontal { width: 18px; height: 18px; - background-image: url(styles/slider-handle.svg); + background-image: url(:/styles/slider-handle.svg); background-repeat: no-repeat; background-position: center center; } @@ -107,7 +107,7 @@ QPushButton#closeButton { border-width: 1px; border-radius: 0; background-color: #fff; - background-image: url(styles/close.svg); + background-image: url(:/styles/close.svg); background-repeat: no-repeat; background-position: center center; } diff --git a/interface/resources/styles/import_dialog.qss b/interface/resources/styles/import_dialog.qss index 8fe04ae1b7..3c2dbdcce9 100644 --- a/interface/resources/styles/import_dialog.qss +++ b/interface/resources/styles/import_dialog.qss @@ -63,17 +63,17 @@ QPushButton#cancelButton { } #backButton { - background-image: url(icons/backButton.svg); + background-image: url(:/icons/backButton.svg); border-radius: 0px; } #forwardButton { - background-image: url(icons/forwardButton.svg); + background-image: url(:/icons/forwardButton.svg); border-radius: 0px; } #toParentButton { - background-image: url(icons/toParentButton.svg); + background-image: url(:/icons/toParentButton.svg); border-radius: 0px; } diff --git a/interface/resources/styles/log_dialog.qss b/interface/resources/styles/log_dialog.qss index 33473d2903..e0ec17549d 100644 --- a/interface/resources/styles/log_dialog.qss +++ b/interface/resources/styles/log_dialog.qss @@ -22,7 +22,7 @@ QLineEdit { } QPushButton#searchButton { - background: url(styles/search.svg); + background: url(:/styles/search.svg); background-repeat: none; background-position: left center; background-origin: content; @@ -55,7 +55,7 @@ QPushButton#searchPrevButton { QPushButton#revealLogButton { font-family: Helvetica, Arial, sans-serif; - background: url(styles/txt-file.svg); + background: url(:/styles/txt-file.svg); background-repeat: none; background-position: left center; background-origin: content; @@ -86,11 +86,11 @@ QCheckBox { } QCheckBox::indicator:unchecked { - image: url(styles/unchecked.svg); + image: url(:/styles/unchecked.svg); } QCheckBox::indicator:checked { - image: url(styles/checked.svg); + image: url(:/styles/checked.svg); } QComboBox { @@ -110,6 +110,6 @@ QComboBox::drop-down { } QComboBox::down-arrow { - image: url(styles/filter.png); + image: url(:/styles/filter.png); border-width: 0px; } \ No newline at end of file From 867bb99b51bde9f36eb9e69ebd5ec5bed7b707d6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Jan 2018 13:04:08 -0800 Subject: [PATCH 65/73] Fix help screen URL --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c2b22b3772..9379c85fdd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2724,7 +2724,7 @@ void Application::showHelp() { queryString.addQueryItem("defaultTab", defaultTab); auto tabletScriptingInterface = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); - tablet->gotoWebScreen(PathUtils::resourcesPath() + INFO_HELP_PATH + "?" + queryString.toString()); + tablet->gotoWebScreen(PathUtils::resourcesUrl() + INFO_HELP_PATH + "?" + queryString.toString()); DependencyManager::get()->openTablet(); //InfoView::show(INFO_HELP_PATH, false, queryString.toString()); } From ab4e2d99f1939b2e5b6e7281ad69dfeea4e92850 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Jan 2018 13:04:54 -0800 Subject: [PATCH 66/73] Don't try to set the current directory to a QRC path --- interface/src/ui/BaseLogDialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp index 6830de6e35..969f9895de 100644 --- a/interface/src/ui/BaseLogDialog.cpp +++ b/interface/src/ui/BaseLogDialog.cpp @@ -39,7 +39,6 @@ BaseLogDialog::BaseLogDialog(QWidget* parent) : QDialog(parent, Qt::Window) { QFile styleSheet(PathUtils::resourcesPath() + "styles/log_dialog.qss"); if (styleSheet.open(QIODevice::ReadOnly)) { - QDir::setCurrent(PathUtils::resourcesPath()); setStyleSheet(styleSheet.readAll()); } From b6f7d2eb892aa0c669a5d90a3f4822a1ff9d6476 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 25 Jan 2018 15:15:59 -0800 Subject: [PATCH 67/73] print warnings if head position contains not-a-number --- interface/src/avatar/MyAvatar.cpp | 37 ++++++++++++++++++++++++------- libraries/animation/src/Rig.cpp | 25 ++++++++++++++------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2e633986a0..a1ed0ff0df 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -561,6 +561,12 @@ void MyAvatar::simulate(float deltaTime) { if (!_skeletonModel->getHeadPosition(headPosition)) { headPosition = getWorldPosition(); } + + if (isNaN(headPosition)) { + qCDebug(interfaceapp) << "MyAvatar::simulate headPosition is NaN"; + headPosition = glm::vec3(0.0f); + } + head->setPosition(headPosition); head->setScale(getModelScale()); head->simulate(deltaTime); @@ -2700,27 +2706,42 @@ void MyAvatar::setWalkSpeed(float value) { } glm::vec3 MyAvatar::getPositionForAudio() { + glm::vec3 result; switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: - return getHead()->getPosition(); + result = getHead()->getPosition(); case AudioListenerMode::FROM_CAMERA: - return qApp->getCamera().getPosition(); + result = qApp->getCamera().getPosition(); case AudioListenerMode::CUSTOM: - return _customListenPosition; + result = _customListenPosition; } - return vec3(); + + if (isNaN(result)) { + qCDebug(interfaceapp) << "MyAvatar::getPositionForAudio produced NaN" << _audioListenerMode; + result = glm::vec3(0.0f); + } + + return result; } glm::quat MyAvatar::getOrientationForAudio() { + glm::quat result; + switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: - return getHead()->getFinalOrientationInWorldFrame(); + result = getHead()->getFinalOrientationInWorldFrame(); case AudioListenerMode::FROM_CAMERA: - return qApp->getCamera().getOrientation(); + result = qApp->getCamera().getOrientation(); case AudioListenerMode::CUSTOM: - return _customListenOrientation; + result = _customListenOrientation; } - return quat(); + + if (isNaN(result)) { + qCDebug(interfaceapp) << "MyAvatar::getOrientationForAudio produced NaN" << _audioListenerMode; + result = glm::quat(); + } + + return result; } void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b1b41775a8..309bb59cff 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -445,22 +445,31 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo } bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { + bool success { false }; if (QThread::currentThread() == thread()) { if (isIndexValid(jointIndex)) { position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation; - return true; + success = true; } else { - return false; + success = false; + } + } else { + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { + position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation; + success = true; + } else { + success = false; } } - QReadLocker readLock(&_externalPoseSetLock); - if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { - position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation; - return true; - } else { - return false; + if (isNaN(position)) { + qCWarning(animation) << "Rig::getJointPositionInWorldFrame produces NaN"; + success = false; + position = glm::vec3(0.0f); } + + return success; } bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const { From 537db37b2634fbf3577da28de665d61b5556e763 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 25 Jan 2018 16:23:35 -0800 Subject: [PATCH 68/73] removed dead comment --- .../dressing_room/.#doppelganger.js | 1 + script-archive/dressing_room/doppelganger.js | 30 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) create mode 100644 script-archive/dressing_room/.#doppelganger.js diff --git a/script-archive/dressing_room/.#doppelganger.js b/script-archive/dressing_room/.#doppelganger.js new file mode 100644 index 0000000000..63b1f8f93c --- /dev/null +++ b/script-archive/dressing_room/.#doppelganger.js @@ -0,0 +1 @@ +dante@0228-DESKTOP-PC.9420:1516899782 \ No newline at end of file diff --git a/script-archive/dressing_room/doppelganger.js b/script-archive/dressing_room/doppelganger.js index 8ff62ff665..773a5d974b 100644 --- a/script-archive/dressing_room/doppelganger.js +++ b/script-archive/dressing_room/doppelganger.js @@ -32,7 +32,7 @@ function Doppelganger(avatar) { script: FREEZE_TOGGLER_SCRIPT_URL, userData: JSON.stringify({ grabbableKey: { - grabbable: true, + grabbable: false, wantsTrigger: true } }) @@ -64,7 +64,6 @@ function setJointData(doppelganger, relativeXforms) { var i, l = relativeXforms.length; for (i = 0; i < l; i++) { jointRotations.push(relativeXforms[i].rot); - //Entities.setAbsoluteJointRotationInObjectFrame(doppelganger.id, i, relativeXforms[i].rot); } Entities.setLocalJointRotations(doppelganger.id, jointRotations); @@ -206,7 +205,7 @@ function Xform(rot, pos) { }; Xform.ident = function () { return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0}); -} +}; Xform.mul = function (lhs, rhs) { var rot = Quat.multiply(lhs.rot, rhs.rot); var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos)); @@ -354,12 +353,12 @@ function getAvatarFootOffset() { if (jointName === "RightToe_End") { toeTop = d.translation.y } - }) + }); var myPosition = MyAvatar.position; var offset = upperLeg + lowerLeg + foot + toe + toeTop; offset = offset / 100; - return offset + return offset; } @@ -545,15 +544,15 @@ function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { baseEntity: baseEntity, doppelganger: doppelganger.id } - }) + }); var mirrorEntity = Entities.addEntity(wearableProps); - var mirrorEntityProps = Entities.getEntityProperties(mirrorEntity) - print('MIRROR PROPS::' + JSON.stringify(mirrorEntityProps)) + var mirrorEntityProps = Entities.getEntityProperties(mirrorEntity); + print('MIRROR PROPS::' + JSON.stringify(mirrorEntityProps)); var wearablePair = { baseEntity: baseEntity, mirrorEntity: mirrorEntity - } + }; wearablePairs.push(wearablePair); } @@ -563,11 +562,11 @@ function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { var mirrorEntity = getMirrorEntityForBaseEntity(baseEntity); // print('MIRROR ENTITY, newPosition' + mirrorEntity + ":::" + JSON.stringify(newPosition)) - Entities.editEntity(mirrorEntity, wearableProps) + Entities.editEntity(mirrorEntity, wearableProps); } if (action === 'remove') { - Entities.deleteEntity(getMirrorEntityForBaseEntity(baseEntity)) + Entities.deleteEntity(getMirrorEntityForBaseEntity(baseEntity)); wearablePairs = wearablePairs.filter(function(obj) { return obj.baseEntity !== baseEntity; }); @@ -575,7 +574,7 @@ function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { if (action === 'updateBase') { //this gets called when the mirrored entity gets grabbed. now we move the - var mirrorEntityProperties = Entities.getEntityProperties(message.mirrorEntity) + var mirrorEntityProperties = Entities.getEntityProperties(message.mirrorEntity); var doppelgangerToMirrorEntity = Vec3.subtract(doppelgangerProps.position, mirrorEntityProperties.position); var newPosition = Vec3.sum(MyAvatar.position, doppelgangerToMirrorEntity); @@ -596,7 +595,7 @@ function getMirrorEntityForBaseEntity(baseEntity) { if (result.length === 0) { return false; } else { - return result[0].mirrorEntity + return result[0].mirrorEntity; } } @@ -607,13 +606,12 @@ function getBaseEntityForMirrorEntity(mirrorEntity) { if (result.length === 0) { return false; } else { - return result[0].baseEntity + return result[0].baseEntity; } } makeDoppelgangerForMyAvatar(); subscribeToWearableMessages(); -//subscribeToWearableMessagesForAvatar(); subscribeToFreezeMessages(); function cleanup() { @@ -622,7 +620,7 @@ function cleanup() { } doppelgangers.forEach(function(doppelganger) { - print('DOPPELGANGER' + doppelganger.id) + print('DOPPELGANGER' + doppelganger.id); Entities.deleteEntity(doppelganger.id); }); } From 5bb1b1bbffb006d651d9f9d74904bec9f725e5d9 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 25 Jan 2018 16:26:00 -0800 Subject: [PATCH 69/73] remove backup file --- script-archive/dressing_room/.#doppelganger.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 script-archive/dressing_room/.#doppelganger.js diff --git a/script-archive/dressing_room/.#doppelganger.js b/script-archive/dressing_room/.#doppelganger.js deleted file mode 100644 index 63b1f8f93c..0000000000 --- a/script-archive/dressing_room/.#doppelganger.js +++ /dev/null @@ -1 +0,0 @@ -dante@0228-DESKTOP-PC.9420:1516899782 \ No newline at end of file From 17ec79f1827482913106f8ff6cdd5e6057a6bbb8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 26 Jan 2018 10:34:43 -0800 Subject: [PATCH 70/73] add breaks in case statements that used to have returns --- interface/src/avatar/MyAvatar.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a1ed0ff0df..12f92f9ea2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2710,10 +2710,13 @@ glm::vec3 MyAvatar::getPositionForAudio() { switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: result = getHead()->getPosition(); + break; case AudioListenerMode::FROM_CAMERA: result = qApp->getCamera().getPosition(); + break; case AudioListenerMode::CUSTOM: result = _customListenPosition; + break; } if (isNaN(result)) { @@ -2730,10 +2733,13 @@ glm::quat MyAvatar::getOrientationForAudio() { switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: result = getHead()->getFinalOrientationInWorldFrame(); + break; case AudioListenerMode::FROM_CAMERA: result = qApp->getCamera().getOrientation(); + break; case AudioListenerMode::CUSTOM: result = _customListenOrientation; + break; } if (isNaN(result)) { From e2afcac53427a5fceeefc96ecfc25146fef65dae Mon Sep 17 00:00:00 2001 From: humbletim Date: Fri, 26 Jan 2018 18:34:13 -0500 Subject: [PATCH 71/73] include .extraInfo as part of RayPick API constructors --- interface/src/raypick/RayPick.cpp | 6 +++--- interface/src/raypick/RayPick.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 4a39d5df59..75b5e77fd8 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -19,7 +19,7 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { DependencyManager::get()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(), getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (entityRes.intersects) { - return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal); + return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } @@ -30,7 +30,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(), getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (overlayRes.intersects) { - return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal); + return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } @@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); if (avatarRes.intersects) { - return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick); + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 0153ea64fc..6bdc2cb5b0 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -18,8 +18,8 @@ class RayPickResult : public PickResult { public: RayPickResult() {} RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} - RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) : - PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) { + RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : + PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) { } RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) { From 08df26247430332b0fa5345125203fa04df708cf Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 27 Jan 2018 11:42:17 -0800 Subject: [PATCH 72/73] Fix haze parameters uniform buffer size --- libraries/graphics/src/graphics/Haze.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/graphics/src/graphics/Haze.h b/libraries/graphics/src/graphics/Haze.h index 608449a97e..eda9f1e407 100644 --- a/libraries/graphics/src/graphics/Haze.h +++ b/libraries/graphics/src/graphics/Haze.h @@ -117,13 +117,14 @@ namespace graphics { // Amount of background (skybox) to display, overriding the haze effect for the background float hazeBackgroundBlend{ INITIAL_HAZE_BACKGROUND_BLEND }; - // The haze attenuation exponents used by both fragment and directional light attenuation float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; float hazeHeightFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; - float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; + float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; + // Padding required to align the structure to sizeof(vec4) + vec3 __padding; Parameters() {} }; From a637bac2fee71c2e4a78f885462bea5c568b81ab Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 29 Jan 2018 11:26:28 -0800 Subject: [PATCH 73/73] remove debug statement --- interface/src/AvatarEntitiesBookmarks.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp index 2a70d1a42f..d108bf3a23 100644 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ b/interface/src/AvatarEntitiesBookmarks.cpp @@ -111,7 +111,6 @@ void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { } void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() { - qDebug() << "AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities"; QAction* action = qobject_cast(sender()); auto myAvatar = DependencyManager::get()->getMyAvatar();