diff --git a/BUILD.md b/BUILD.md index feed677828..ba38f4b51d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ### Dependencies - [cmake](https://cmake.org/download/): 3.9 -- [Qt](https://www.qt.io/download-open-source): 5.9.1 +- [Qt](https://www.qt.io/download-open-source): 5.10.1 - [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities. - [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -46,8 +46,8 @@ This can either be entered directly into your shell session before you build or The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake #### Generating build files @@ -66,7 +66,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/lib/cmake #### Finding Dependencies diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 038f53154c..417ed9b8de 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -14,8 +14,8 @@ Should you choose not to install Qt5 via a package manager that handles dependen Install qt: ```bash -wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.6.1_5.6.1_amd64.deb -sudo dpkg -i hifi-qt5.6.1_5.6.1_amd64.deb +wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb +sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb ``` Install build dependencies: @@ -66,7 +66,7 @@ cd hifi/build Prepare makefiles: ```bash -cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.6.1/5.6/gcc_64/lib/cmake .. +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake .. ``` Start compilation and get a cup of coffee: @@ -74,7 +74,7 @@ Start compilation and get a cup of coffee: make domain-server assignment-client interface ``` -In a server does not make sense to compile interface +In a server does not make sense to compile interface ### Running the software diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 6b66863534..62102b3e18 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -20,7 +20,7 @@ Note that this uses the version from the homebrew formula at the time of this wr Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations. For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH: - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.10.1/lib/cmake Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index eea1f85e5b..3222e75c66 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -1,13 +1,13 @@ This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit. ## Building High Fidelity -Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. +Note: We are now using Visual Studio 2017 and Qt 5.10.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. ### Step 1. Visual Studio 2017 -If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). +If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right. @@ -17,15 +17,15 @@ Download and install the latest version of CMake 3.9. Download the file named w ### Step 3. Installing Qt -Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.9.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)". +Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)". -Note: Installing the Sources is optional but recommended if you have room for them (~2GB). +Note: Installing the Sources is optional but recommended if you have room for them (~2GB). ### Step 4. Setting Qt Environment Variable Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search). * Set "Variable name": `QT_CMAKE_PREFIX_PATH` -* Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake` +* Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake` ### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg) @@ -39,7 +39,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables * In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows` * Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl` - + ### Step 7. Running CMake to Generate Build Files Run Command Prompt from Start and run the following commands: @@ -49,7 +49,7 @@ mkdir build cd build cmake .. -G "Visual Studio 15 Win64" ``` - + Where `%HIFI_DIR%` is the directory for the highfidelity repository. ### Step 8. Making a Build @@ -74,10 +74,10 @@ Note: You can also run Interface by launching it from command line or File Explo ## Troubleshooting -For any problems after Step #7, first try this: +For any problems after Step #7, first try this: * Delete your locally cloned copy of the highfidelity repository * Restart your computer -* Redownload the [repository](https://github.com/highfidelity/hifi) +* Redownload the [repository](https://github.com/highfidelity/hifi) * Restart directions from Step #7 #### CMake gives you the same error message repeatedly after the build fails @@ -90,4 +90,4 @@ Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. Verify that #### Qt is throwing an error -Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. +Make sure you have the correct version (5.10.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d95ac446f..93b784b462 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ set(BUILD_TOOLS_OPTION ON) set(BUILD_INSTALLER_OPTION ON) set(GLES_OPTION OFF) set(DISABLE_QML_OPTION OFF) +set(DOWNLOAD_SERVERLESS_CONTENT_OPTION OFF) if (ANDROID OR UWP) set(BUILD_SERVER_OPTION OFF) @@ -74,6 +75,11 @@ option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION}) option(USE_GLES "Use OpenGL ES" ${GLES_OPTION}) option(DISABLE_QML "Disable QML" ${DISABLE_QML_OPTION}) option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF) +option( + DOWNLOAD_SERVERLESS_CONTENT + "Download and setup default serverless content beside Interface" + ${DOWNLOAD_SERVERLESS_CONTENT_OPTION} +) set(PLATFORM_QT_GL OpenGL) @@ -88,12 +94,13 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}") endforeach() -MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) -MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) -MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) -MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS}) -MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER}) -MESSAGE(STATUS "GL ES: " ${USE_GLES}) +MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) +MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) +MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) +MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS}) +MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER}) +MESSAGE(STATUS "GL ES: " ${USE_GLES}) +MESSAGE(STATUS "DL serverless content: " ${DOWNLOAD_SERVERLESS_CONTENT}) if (DISABLE_QML) MESSAGE(STATUS "QML disabled!") diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 9d474c8c24..22ed01fd00 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -46,15 +46,68 @@ static const uint8_t CPU_AFFINITY_COUNT_LOW = 1; static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000; #endif -const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; - static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" }; static QStringList BAKEABLE_TEXTURE_EXTENSIONS; -static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {}; +static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = { }; + 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"; +static const ModelBakeVersion CURRENT_MODEL_BAKE_VERSION = (ModelBakeVersion)((BakeVersion)ModelBakeVersion::COUNT - 1); +static const TextureBakeVersion CURRENT_TEXTURE_BAKE_VERSION = (TextureBakeVersion)((BakeVersion)TextureBakeVersion::COUNT - 1); +static const ScriptBakeVersion CURRENT_SCRIPT_BAKE_VERSION = (ScriptBakeVersion)((BakeVersion)ScriptBakeVersion::COUNT - 1); + +BakedAssetType assetTypeForExtension(const QString& extension) { + auto extensionLower = extension.toLower(); + if (BAKEABLE_MODEL_EXTENSIONS.contains(extensionLower)) { + return BakedAssetType::Model; + } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extensionLower.toLocal8Bit())) { + return BakedAssetType::Texture; + } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extensionLower)) { + return BakedAssetType::Script; + } + return BakedAssetType::Undefined; +} + +BakedAssetType assetTypeForFilename(const QString& filename) { + auto dotIndex = filename.lastIndexOf("."); + if (dotIndex == -1) { + return BakedAssetType::Undefined; + } + + auto extension = filename.mid(dotIndex + 1); + return assetTypeForExtension(extension); +} + +QString bakedFilenameForAssetType(BakedAssetType type) { + switch (type) { + case BakedAssetType::Model: + return BAKED_MODEL_SIMPLE_NAME; + case BakedAssetType::Texture: + return BAKED_TEXTURE_SIMPLE_NAME; + case BakedAssetType::Script: + return BAKED_SCRIPT_SIMPLE_NAME; + default: + return ""; + } +} + +BakeVersion currentBakeVersionForAssetType(BakedAssetType type) { + switch (type) { + case BakedAssetType::Model: + return (BakeVersion)CURRENT_MODEL_BAKE_VERSION; + case BakedAssetType::Texture: + return (BakeVersion)CURRENT_TEXTURE_BAKE_VERSION; + case BakedAssetType::Script: + return (BakeVersion)CURRENT_SCRIPT_BAKE_VERSION; + default: + return 0; + } +} + +const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; + 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); @@ -167,36 +220,38 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU return false; } - auto dotIndex = path.lastIndexOf("."); - if (dotIndex == -1) { + BakedAssetType type = assetTypeForFilename(path); + + if (type == BakedAssetType::Undefined) { return false; } - auto extension = path.mid(dotIndex + 1); + QString bakedFilename = bakedFilenameForAssetType(type); + auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; + auto mappingIt = _fileMappings.find(bakedPath); + bool bakedMappingExists = mappingIt != _fileMappings.end(); - QString bakedFilename; + // If the path is mapped to the original file's hash, baking has been disabled for this + // asset + if (bakedMappingExists && mappingIt->second == assetHash) { + return false; + } bool loaded; AssetMeta meta; std::tie(loaded, meta) = readMetaFile(assetHash); - // TODO: Allow failed bakes that happened on old versions to be re-baked - if (loaded && meta.failedLastBake) { + if (type == BakedAssetType::Texture && !loaded) { return false; } - if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; - } else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) { - bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; - } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; - } else { + auto currentVersion = currentBakeVersionForAssetType(type); + + if (loaded && (meta.failedLastBake && meta.bakeVersion >= currentVersion)) { return false; } - auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; - return _fileMappings.find(bakedPath) == _fileMappings.end(); + return !bakedMappingExists || (meta.bakeVersion < currentVersion); } bool interfaceRunning() { @@ -598,15 +653,9 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi // first, figure out from the mapping extension what type of file this is auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower(); - QString bakedRootFile; - if (BAKEABLE_MODEL_EXTENSIONS.contains(assetPathExtension)) { - bakedRootFile = BAKED_MODEL_SIMPLE_NAME; - } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) { - bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME; - } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(assetPathExtension)) { - bakedRootFile = BAKED_SCRIPT_SIMPLE_NAME; - } + auto type = assetTypeForFilename(assetPath); + QString bakedRootFile = bakedFilenameForAssetType(type); auto originalAssetHash = it->second; QString redirectedAssetHash; @@ -653,9 +702,19 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi auto query = QUrlQuery(url.query()); bool isSkybox = query.hasQueryItem("skybox"); if (isSkybox) { - writeMetaFile(originalAssetHash); - if (!bakingDisabled) { - maybeBake(assetPath, originalAssetHash); + bool loaded; + AssetMeta meta; + std::tie(loaded, meta) = readMetaFile(originalAssetHash); + + if (!loaded) { + AssetMeta needsBakingMeta; + needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION; + + writeMetaFile(originalAssetHash, needsBakingMeta); + if (!bakingDisabled) { + maybeBake(assetPath, originalAssetHash); + } + } } } @@ -1275,15 +1334,19 @@ QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativ } void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) { - qDebug() << "Failed: " << originalAssetHash << assetPath << errors; + qDebug() << "Failed to bake: " << originalAssetHash << assetPath << "(" << errors << ")"; bool loaded; AssetMeta meta; std::tie(loaded, meta) = readMetaFile(originalAssetHash); + auto type = assetTypeForFilename(assetPath); + auto currentTypeVersion = currentBakeVersionForAssetType(type); + meta.failedLastBake = true; meta.lastBakeErrors = errors; + meta.bakeVersion = currentTypeVersion; writeMetaFile(originalAssetHash, meta); @@ -1373,17 +1436,20 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir; } - if (!errorCompletingBake) { - // create the meta file to store which version of the baking process we just completed - writeMetaFile(originalAssetHash); - } else { + auto type = assetTypeForFilename(originalAssetPath); + auto currentTypeVersion = currentBakeVersionForAssetType(type); + + AssetMeta meta; + meta.bakeVersion = currentTypeVersion; + meta.failedLastBake = errorCompletingBake; + + if (errorCompletingBake) { qWarning() << "Could not complete bake for" << originalAssetHash; - AssetMeta meta; - meta.failedLastBake = true; meta.lastBakeErrors = errorReason; - writeMetaFile(originalAssetHash, meta); } + writeMetaFile(originalAssetHash, meta); + _pendingBakes.remove(originalAssetHash); } @@ -1420,16 +1486,16 @@ std::pair AssetServer::readMetaFile(AssetUtils::AssetHash hash) if (error.error == QJsonParseError::NoError && doc.isObject()) { auto root = doc.object(); - auto bakeVersion = root[BAKE_VERSION_KEY].toInt(-1); + auto bakeVersion = root[BAKE_VERSION_KEY]; auto failedLastBake = root[FAILED_LAST_BAKE_KEY]; auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY]; - if (bakeVersion != -1 + if (bakeVersion.isDouble() && failedLastBake.isBool() && lastBakeErrors.isString()) { AssetMeta meta; - meta.bakeVersion = bakeVersion; + meta.bakeVersion = bakeVersion.toInt(); meta.failedLastBake = failedLastBake.toBool(); meta.lastBakeErrors = lastBakeErrors.toString(); @@ -1447,7 +1513,7 @@ bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const A // construct the JSON that will be in the meta file QJsonObject metaFileObject; - metaFileObject[BAKE_VERSION_KEY] = meta.bakeVersion; + metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion; metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake; metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors; @@ -1479,27 +1545,13 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool for (const auto& path : paths) { auto it = _fileMappings.find(path); if (it != _fileMappings.end()) { + auto type = assetTypeForFilename(path); + if (type == BakedAssetType::Undefined) { + continue; + } + QString bakedFilename = bakedFilenameForAssetType(type); + auto hash = it->second; - - auto dotIndex = path.lastIndexOf("."); - if (dotIndex == -1) { - continue; - } - - auto extension = path.mid(dotIndex + 1); - - QString bakedFilename; - - if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_MODEL_SIMPLE_NAME; - } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) { - bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; - } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { - bakedFilename = BAKED_SCRIPT_SIMPLE_NAME; - } else { - continue; - } - auto bakedMapping = getBakeMapping(hash, bakedFilename); auto it = _fileMappings.find(bakedMapping); diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 00ab27c74d..fb88df0171 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -23,8 +23,47 @@ #include "RegisteredMetaTypes.h" +using BakeVersion = int; +static const BakeVersion INITIAL_BAKE_VERSION = 0; +static const BakeVersion NEEDS_BAKING_BAKE_VERSION = -1; + +enum class BakedAssetType : int { + Model = 0, + Texture, + Script, + + NUM_ASSET_TYPES, + Undefined +}; + +// ATTENTION! If you change the current version for an asset type, you will also +// need to update the function currentBakeVersionForAssetType() inside of AssetServer.cpp. +enum class ModelBakeVersion : BakeVersion { + Initial = INITIAL_BAKE_VERSION, + + COUNT +}; + +// ATTENTION! See above. +enum class TextureBakeVersion : BakeVersion { + Initial = INITIAL_BAKE_VERSION, + + COUNT +}; + +// ATTENTION! See above. +enum class ScriptBakeVersion : BakeVersion { + Initial = INITIAL_BAKE_VERSION, + FixEmptyScripts, + + COUNT +}; + struct AssetMeta { - int bakeVersion { 0 }; + AssetMeta() { + } + + BakeVersion bakeVersion; bool failedLastBake { false }; QString lastBakeErrors; }; diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index ccdd2fca4f..3c8e38f278 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -322,8 +322,16 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU // stereo sources are not passed through HRTF if (streamToAdd.isStereo()) { - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) { - _mixSamples[i] += float(streamPopOutput[i] * gain / AudioConstants::MAX_SAMPLE_VALUE); + + // apply the avatar gain adjustment + auto& hrtf = listenerNodeData.hrtfForStream(sourceNodeID, streamToAdd.getStreamIdentifier()); + gain *= hrtf.getGainAdjustment(); + + const float scale = 1/32768.0f; // int16_t to float + + for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { + _mixSamples[2*i+0] += (float)streamPopOutput[2*i+0] * gain * scale; + _mixSamples[2*i+1] += (float)streamPopOutput[2*i+1] * gain * scale; } ++stats.manualStereoMixes; @@ -332,10 +340,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU // echo sources are not passed through HRTF if (isEcho) { - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i += 2) { - auto monoSample = float(streamPopOutput[i / 2] * gain / AudioConstants::MAX_SAMPLE_VALUE); - _mixSamples[i] += monoSample; - _mixSamples[i + 1] += monoSample; + + const float scale = 1/32768.0f; // int16_t to float + + for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { + float sample = (float)streamPopOutput[i] * gain * scale; + _mixSamples[2*i+0] += sample; + _mixSamples[2*i+1] += sample; } ++stats.manualEchoMixes; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 288652715a..268aba62d6 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) return 0; } -void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) { +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) { std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { itr->second = time; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 7a7210a0e8..3c2e660cbc 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -113,7 +113,7 @@ public: ViewFrustum getViewFrustum() const { return _currentViewFrustum; } uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; - void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time); + void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar]; diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index e394884dc2..e3aca52ee5 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -363,7 +363,9 @@ void EntityServer::nodeAdded(SharedNodePointer node) { void EntityServer::nodeKilled(SharedNodePointer node) { EntityTreePointer tree = std::static_pointer_cast(_tree); - tree->deleteDescendantsOfAvatar(node->getUUID()); + tree->withWriteLock([&] { + tree->deleteDescendantsOfAvatar(node->getUUID()); + }); tree->forgetAvatarID(node->getUUID()); OctreeServer::nodeKilled(node); } @@ -451,8 +453,6 @@ void EntityServer::domainSettingsRequestFailed() { void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Starting Dynamic Domain Verification..."; - QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); - EntityTreePointer tree = std::static_pointer_cast(_tree); QHash localMap(tree->getEntityCertificateIDMap()); @@ -460,17 +460,20 @@ void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap"; while (i.hasNext()) { i.next(); + const auto& certificateID = i.key(); + const auto& entityID = i.value(); - EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); + EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); if (entity) { if (!entity->getProperties().verifyStaticCertificateProperties()) { - qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" + qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed" << "static certificate verification."; // Delete the entity if it doesn't pass static certificate verification - tree->deleteEntity(i.value(), true); + tree->withWriteLock([&] { + tree->deleteEntity(entityID, true); + }); } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -478,35 +481,46 @@ void EntityServer::startDynamicDomainVerification() { QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); QJsonObject request; - request["certificate_id"] = i.key(); + request["certificate_id"] = certificateID; networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] { + EntityTreePointer tree = std::static_pointer_cast(_tree); - connect(networkReply, &QNetworkReply::finished, [=]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); jsonObject = jsonObject["data"].toObject(); if (networkReply->error() == QNetworkReply::NoError) { + QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); if (jsonObject["domain_id"].toString() != thisDomainID) { - qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() - << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value(); - tree->deleteEntity(i.value(), true); + EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); + if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) { + qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() + << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; + tree->withWriteLock([&] { + tree->deleteEntity(entityID, true); + }); + } else { + qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID; + } } else { - qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value(); + qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID; } } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value() + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID << "More info:" << jsonObject; - tree->deleteEntity(i.value(), true); + tree->withWriteLock([&] { + tree->deleteEntity(entityID, true); + }); } networkReply->deleteLater(); }); } } else { - qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!"; + qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!"; } } diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 94d21f1c9a..4aa52922c0 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -311,7 +311,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } - } else if (entity->getLastEdited() > knownTimestamp->second) { + } else if (entity->getLastEdited() > knownTimestamp->second + || entity->getLastChangedOnServer() > knownTimestamp->second) { // it is known and it changed --> put it on the queue with any priority // TODO: sort these correctly _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); @@ -330,7 +331,9 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree return; } auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { + if (knownTimestamp == _knownState.end() + || entity->getLastEdited() > knownTimestamp->second + || entity->getLastChangedOnServer() > knownTimestamp->second) { _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } @@ -382,7 +385,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } - } else if (entity->getLastEdited() > knownTimestamp->second) { + } else if (entity->getLastEdited() > knownTimestamp->second + || entity->getLastChangedOnServer() > knownTimestamp->second) { // it is known and it changed --> put it on the queue with any priority // TODO: sort these correctly _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 594f423838..1e2bd15429 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -48,8 +48,6 @@ private: void preDistributionProcessing() override; bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); } bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); } - void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {}; - bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; } DiffTraversal _traversal; EntityPriorityQueue _sendQueue; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index d5b9da7353..de49bd461c 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -304,23 +304,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* return numPackets; } -void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) { - // If we're starting a full scene, then definitely we want to empty the elementBag - if (isFullScene) { - nodeData->elementBag.deleteAll(); - } - - // This is the start of "resending" the scene. - bool dontRestartSceneOnMove = false; // this is experimental - if (dontRestartSceneOnMove) { - if (nodeData->elementBag.isEmpty()) { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } - } else { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } -} - /// Version of octree element distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { OctreeServer::didPacketDistributor(this); @@ -366,16 +349,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // the current view frustum for things to send. if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { - // if our view has changed, we need to reset these things... - if (viewFrustumChanged) { - if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) { - nodeData->dumpOutOfView(); - } - } - // track completed scenes and send out the stats packet accordingly nodeData->stats.sceneCompleted(); - nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged()); _myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData); // TODO: add these to stats page @@ -389,109 +364,74 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; - nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE); // start tracking our stats nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot()); - - preStartNewScene(nodeData, isFullScene); } - // If we have something in our elementBag, then turn them into packets and send them out... - if (shouldTraverseAndSend(nodeData)) { - quint64 start = usecTimestampNow(); + quint64 start = usecTimestampNow(); + _myServer->getOctree()->withReadLock([&]{ traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + }); - // Here's where we can/should allow the server to send other data... - // send the environment packet - // TODO: should we turn this into a while loop to better handle sending multiple special packets - if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { - int specialPacketsSent = 0; - int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); - nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed - _truePacketsSent += specialPacketsSent; - _trueBytesSent += specialBytesSent; - _packetsSentThisInterval += specialPacketsSent; + // Here's where we can/should allow the server to send other data... + // send the environment packet + // TODO: should we turn this into a while loop to better handle sending multiple special packets + if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { + int specialPacketsSent = 0; + int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); + nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed + _truePacketsSent += specialPacketsSent; + _trueBytesSent += specialBytesSent; + _packetsSentThisInterval += specialPacketsSent; - _totalPackets += specialPacketsSent; - _totalBytes += specialBytesSent; + _totalPackets += specialPacketsSent; + _totalBytes += specialBytesSent; - _totalSpecialPackets += specialPacketsSent; - _totalSpecialBytes += specialBytesSent; + _totalSpecialPackets += specialPacketsSent; + _totalSpecialBytes += specialBytesSent; + } + + // calculate max number of packets that can be sent during this interval + int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + + // Re-send packets that were nacked by the client + while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { + const NLPacket* packet = nodeData->getNextNackedPacket(); + if (packet) { + DependencyManager::get()->sendUnreliablePacket(*packet, *node); + int numBytes = packet->getDataSize(); + _truePacketsSent++; + _trueBytesSent += numBytes; + _packetsSentThisInterval++; + + _totalPackets++; + _totalBytes += numBytes; + _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); } + } - // calculate max number of packets that can be sent during this interval - int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); - int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + quint64 end = usecTimestampNow(); + int elapsedmsec = (end - start) / USECS_PER_MSEC; + OctreeServer::trackLoopTime(elapsedmsec); - // Re-send packets that were nacked by the client - while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { - const NLPacket* packet = nodeData->getNextNackedPacket(); - if (packet) { - DependencyManager::get()->sendUnreliablePacket(*packet, *node); - int numBytes = packet->getDataSize(); - _truePacketsSent++; - _trueBytesSent += numBytes; - _packetsSentThisInterval++; + // if we've sent everything, then we want to remember that we've sent all + // the octree elements from the current view frustum + if (!hasSomethingToSend(nodeData)) { + nodeData->setViewSent(true); - _totalPackets++; - _totalBytes += numBytes; - _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); - } + // If this was a full scene then make sure we really send out a stats packet at this point so that + // the clients will know the scene is stable + if (isFullScene) { + nodeData->stats.sceneCompleted(); + handlePacketSend(node, nodeData, true); } - - quint64 end = usecTimestampNow(); - int elapsedmsec = (end - start) / USECS_PER_MSEC; - OctreeServer::trackLoopTime(elapsedmsec); - - // if after sending packets we've emptied our bag, then we want to remember that we've sent all - // the octree elements from the current view frustum - if (!hasSomethingToSend(nodeData)) { - nodeData->updateLastKnownViewFrustum(); - nodeData->setViewSent(true); - - // If this was a full scene then make sure we really send out a stats packet at this point so that - // the clients will know the scene is stable - if (isFullScene) { - nodeData->stats.sceneCompleted(); - handlePacketSend(node, nodeData, true); - } - } - - } // end if bag wasn't empty, and so we sent stuff... + } return _truePacketsSent; } -bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { - bool somethingToSend = false; - OctreeQueryNode* nodeData = static_cast(params.nodeData); - if (!nodeData->elementBag.isEmpty()) { - quint64 encodeStart = usecTimestampNow(); - quint64 lockWaitStart = encodeStart; - - _myServer->getOctree()->withReadLock([&]{ - OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart)); - - OctreeElementPointer subTree = nodeData->elementBag.extract(); - if (subTree) { - // NOTE: this is where the tree "contents" are actually packed - nodeData->stats.encodeStarted(); - _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); - nodeData->stats.encodeStopped(); - - somethingToSend = true; - } - }); - - OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); - } else { - OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME); - OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); - } - return somethingToSend; -} - void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // calculate max number of packets that can be sent during this interval int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); @@ -500,21 +440,12 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre int extraPackingAttempts = 0; // init params once outside the while loop - int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - int boundaryLevelAdjust = boundaryLevelAdjustClient + - (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - float octreeSizeScale = nodeData->getOctreeSizeScale(); - EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, - viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, - isFullScene, nodeData); + EncodeBitstreamParams params(WANT_EXISTS_BITS, nodeData); // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, _nodeUuid); }; nodeData->copyCurrentViewFrustum(params.viewFrustum); - if (viewFrustumChanged) { - nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); - } bool somethingToSend = true; // assume we have something bool hadSomething = hasSomethingToSend(nodeData); @@ -534,8 +465,8 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre extraPackingAttempts++; } - // If the bag had contents but is now empty then we know we've sent the entire scene. - bool completedScene = hadSomething && nodeData->elementBag.isEmpty(); + // If we had something to send, but now we don't, then we know we've sent the entire scene. + bool completedScene = hadSomething; if (completedScene || lastNodeDidntFit) { // we probably want to flush what has accumulated in nodeData but: // do we have more data to send? and is there room? diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 220952e209..91c0ec7adc 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -54,7 +54,7 @@ protected: virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); - virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); + virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0; OctreePacketData _packetData; QWeakPointer _node; @@ -63,14 +63,12 @@ protected: private: /// Called before a packetDistributor pass to allow for pre-distribution processing - virtual void preDistributionProcessing() {}; + virtual void preDistributionProcessing() = 0; int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); - virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); } - virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); } - virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene); - virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); } + virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) = 0; + virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) = 0; int _truePacketsSent { 0 }; // available for debug stats int _trueBytesSent { 0 }; // available for debug stats diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 0dbd24fe9c..fad2c1f026 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -876,10 +876,6 @@ void OctreeServer::parsePayload() { } } -OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePointer& node) { - return std::unique_ptr(new OctreeSendThread(this, node)); -} - OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) { auto sendThread = newSendThread(node); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index e7efc731f2..b25e537d70 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -174,7 +174,7 @@ protected: void beginRunning(QByteArray replaceData); UniqueSendThread createSendThread(const SharedNodePointer& node); - virtual UniqueSendThread newSendThread(const SharedNodePointer& node); + virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0; int _argc; const char** _argv; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 1255c18e71..d242b393bf 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -105,8 +105,6 @@ EntityScriptServer::~EntityScriptServer() { static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server"; void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) { - // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them - // about each other. if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) { auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID)); @@ -119,8 +117,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) { - // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them - // about each other. if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) { MessageID messageID; message->readPrimitive(&messageID); @@ -190,15 +186,14 @@ void EntityScriptServer::updateEntityPPS() { } void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) { - // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them - // about each other. + bool canRezAny = senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified(); bool enable = false; message->readPrimitive(&enable); auto senderUUID = senderNode->getUUID(); auto it = _logListeners.find(senderUUID); - if (enable && senderNode->getCanRez()) { + if (enable && canRezAny) { if (it == std::end(_logListeners)) { _logListeners.insert(senderUUID); qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream"; diff --git a/cmake/externals/crashpad/CMakeLists.txt b/cmake/externals/crashpad/CMakeLists.txt index c464dcbc1b..e509e115e4 100644 --- a/cmake/externals/crashpad/CMakeLists.txt +++ b/cmake/externals/crashpad/CMakeLists.txt @@ -6,8 +6,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://backtrace.io/download/crashpad_062317.zip - URL_MD5 65817e564b3628492abfc1dbd2a1e98b + URL http://public.highfidelity.com/dependencies/crashpad_062317.1.zip + URL_MD5 9c84b77f5f23daf939da1371825ed2b1 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt new file mode 100644 index 0000000000..a08b589ec2 --- /dev/null +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -0,0 +1,16 @@ +include(ExternalProject) + +set(EXTERNAL_NAME serverless-content) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v4.zip + URL_MD5 d4f42f630986c83427ff39e1fe9908c6 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for IDE users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 742c5b5b94..acafd9b6c7 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -14,12 +14,24 @@ macro(GENERATE_INSTALLERS) set(CPACK_MODULE_PATH ${CPACK_MODULE_PATH} "${HF_CMAKE_DIR}/templates") - set(_DISPLAY_NAME ${BUILD_ORGANIZATION}) + + if (CLIENT_ONLY) + set(_PACKAGE_NAME_EXTRA "-Interface") + set(INSTALLER_TYPE "client_only") + string(REGEX REPLACE "High Fidelity" "High Fidelity Interface" _DISPLAY_NAME ${BUILD_ORGANIZATION}) + elseif (SERVER_ONLY) + set(_PACKAGE_NAME_EXTRA "-Sandbox") + set(INSTALLER_TYPE "server_only") + string(REGEX REPLACE "High Fidelity" "High Fidelity Sandbox" _DISPLAY_NAME ${BUILD_ORGANIZATION}) + else () + set(_DISPLAY_NAME ${BUILD_ORGANIZATION}) + set(INSTALLER_TYPE "full") + endif () set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME}) set(CPACK_PACKAGE_VENDOR "High Fidelity") set(CPACK_PACKAGE_VERSION ${BUILD_VERSION}) - set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta-${BUILD_VERSION}") + set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}") set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME}) if (PR_BUILD) diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 601fbdaa20..3ca5cb0e54 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -73,6 +73,11 @@ macro(SET_PACKAGING_PARAMETERS) add_definitions(-DDEV_BUILD) endif () + if (DEPLOY_PACKAGE) + # for deployed packages always grab the serverless content + set(DOWNLOAD_SERVERLESS_CONTENT ON) + endif () + if (APPLE) set(DMG_SUBFOLDER_NAME "${BUILD_ORGANIZATION}") diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake index 00a398761b..71f5314d2f 100644 --- a/cmake/macros/SetupQt.cmake +++ b/cmake/macros/SetupQt.cmake @@ -1,10 +1,10 @@ -# +# # Created by Bradley Austin Davis on 2017/09/02 # Copyright 2013-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 -# +# # Construct a default QT location from a root path, a version and an architecture function(calculate_default_qt_dir _RESULT_NAME) @@ -27,7 +27,7 @@ function(calculate_default_qt_dir _RESULT_NAME) endif() set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT}) - set_from_env(QT_VERSION QT_VERSION "5.9.1") + set_from_env(QT_VERSION QT_VERSION "5.10.1") set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH}) set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE) @@ -60,11 +60,11 @@ macro(setup_qt) #if (NOT EXISTS "${QT_DIR}/include/QtCore/QtGlobal") # message(FATAL_ERROR "Unable to locate Qt includes in ${QT_DIR}") #endif() - + if (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}/Qt5Core/Qt5CoreConfig.cmake") message(FATAL_ERROR "Unable to locate Qt cmake config in ${QT_CMAKE_PREFIX_PATH}") endif() - + message(STATUS "The Qt build in use is: \"${QT_DIR}\"") # Instruct CMake to run moc automatically when needed. @@ -72,7 +72,7 @@ macro(setup_qt) # Instruct CMake to run rcc automatically when needed set(CMAKE_AUTORCC ON) - + if (WIN32) add_paths_to_fixup_libs("${QT_DIR}/bin") endif () diff --git a/cmake/modules/FindGverb.cmake b/cmake/modules/FindGverb.cmake deleted file mode 100644 index 0c149a7ca1..0000000000 --- a/cmake/modules/FindGverb.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# FindGVerb.cmake -# -# Try to find the Gverb library. -# -# You must provide a GVERB_ROOT_DIR which contains src and include directories -# -# Once done this will define -# -# GVERB_FOUND - system found Gverb -# GVERB_INCLUDE_DIRS - the Gverb include directory -# -# Copyright 2014 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake") -hifi_library_search_hints("gverb") - -find_path(GVERB_INCLUDE_DIRS gverb.h PATH_SUFFIXES include HINTS ${GVERB_SEARCH_DIRS}) -find_library(GVERB_LIBRARIES gverb PATH_SUFFIXES lib HINTS ${GVERB_SEARCH_DIRS}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Gverb DEFAULT_MSG GVERB_INCLUDE_DIRS GVERB_LIBRARIES) \ No newline at end of file diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index 9c303f7532..80d86ac030 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -48,3 +48,4 @@ set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@") set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@") set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@") set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@") +set(INSTALLER_TYPE "@INSTALLER_TYPE@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 7faa67d1b0..28ac320e42 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -710,11 +710,9 @@ Function PostInstallOptionsPage !insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" - Pop $CleanInstallCheckbox - IntOp $CurrentOffset $CurrentOffset + 15 - ${EndIf} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" + Pop $CleanInstallCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 ${If} @PR_BUILD@ == 1 ; a PR build defaults all install options expect LaunchServerNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked @@ -809,10 +807,8 @@ Function ReadPostInstallOptions ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState ${EndIf} - ${If} @CLIENT_COMPONENT_CONDITIONAL@ - ; check if the user asked for a clean install - ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState - ${EndIf} + ; check if the user asked for a clean install + ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState FunctionEnd Function HandlePostInstallOptions @@ -856,13 +852,23 @@ Function HandlePostInstallOptions ${EndIf} ${EndIf} - ${If} @CLIENT_COMPONENT_CONDITIONAL@ - ; check if the user asked for a clean install - ${If} $CleanInstallState == ${BST_CHECKED} - SetShellVarContext current - RMDir /r "$APPDATA\@BUILD_ORGANIZATION@" - RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@" + ; check if the user asked for a clean install + ${If} $CleanInstallState == ${BST_CHECKED} + SetShellVarContext current + + ${If} @SERVER_COMPONENT_CONDITIONAL@ + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\Server Console" + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\assignment-client" + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\domain-server" + Delete "$APPDATA\@BUILD_ORGANIZATION@\domain-server.json" ${EndIf} + + ${If} @CLIENT_COMPONENT_CONDITIONAL@ + Delete "$APPDATA\@BUILD_ORGANIZATION@\Interface\AccountInfo.bin" + Delete "$APPDATA\@BUILD_ORGANIZATION@\Interface.json" + ${EndIf} + + RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@" ${EndIf} ${If} @PR_BUILD@ == 1 @@ -976,6 +982,13 @@ Section "-Core installation" ;Store installation folder WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + ;Write some information about this install to the installation folder + FileOpen $0 "$INSTDIR\installer.ini" w + FileWrite $0 "type=@INSTALLER_TYPE@$\r$\n" + FileWrite $0 "campaign=$CampaignName$\r$\n" + FileWrite $0 "exepath=$EXEPATH$\r$\n" + FileClose $0 + ;Package the signed uninstaller produced by the inner loop !ifndef INNER ; this packages the signed uninstaller @@ -1221,6 +1234,9 @@ Section "Uninstall" @CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + ;Remove the installer information file + Delete "$INSTDIR\installer.ini" + ;Remove files we installed. ;Keep the list of directories here in sync with the File commands above. @CPACK_NSIS_DELETE_FILES@ diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2fa86cc860..c180296104 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -611,23 +611,13 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) { // let the NodeList do its checks now (but pass it the sourceNode so it doesn't need to look it up again) return nodeList->isPacketVerifiedWithSource(packet, sourceNode.data()); } else { - static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unmatched IP for UUID"; - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); - - qDebug() << "Packet of type" << headerType - << "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceNode->getUUID()); - + HIFI_FDEBUG("Packet of type" << headerType + << "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceNode->getUUID())); return false; } } else { - static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with Local ID"; - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); - - qDebug() << "Packet of type" << headerType - << "received from unknown node with Local ID" << localSourceID; - + HIFI_FDEBUG("Packet of type" << headerType + << "received from unknown node with Local ID" << localSourceID); return false; } } @@ -1046,41 +1036,7 @@ void DomainServer::processListRequestPacket(QSharedPointer mess bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { auto nodeAData = static_cast(nodeA->getLinkedData()); - auto nodeBData = static_cast(nodeB->getLinkedData()); - - // if we have no linked data for node A then B can't possibly be in the interest set - if (!nodeAData) { - return false; - } - - // first check if the general interest set A contains the type for B - if (nodeAData->getNodeInterestSet().contains(nodeB->getType())) { - // given that there is a match in the general interest set, do any special checks - - // (1/19/17) Agents only need to connect to Entity Script Servers to perform administrative tasks - // related to entity server scripts. Only agents with rez permissions should be doing that, so - // if the agent does not have those permissions, we do not want them and the server to incur the - // overhead of connecting to one another. Additionally we exclude agents that do not care about the - // Entity Script Server and won't attempt to connect to it. - - bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent - && nodeB->getType() == NodeType::EntityScriptServer - && !nodeA->getCanRez() && !nodeA->getCanRezTmp() - && !nodeA->getCanRezCertified() && !nodeA->getCanRezTmpCertified(); - - if (isAgentWithoutRights) { - return false; - } - - bool isScriptServerForIneffectiveAgent = - (nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent) - && ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer)) - || (!nodeB->getCanRez() && !nodeB->getCanRezTmp() && !nodeB->getCanRezCertified() && !nodeB->getCanRezTmpCertified())); - - return !isScriptServerForIneffectiveAgent; - } else { - return false; - } + return nodeAData && nodeAData->getNodeInterestSet().contains(nodeB->getType()); } unsigned int DomainServer::countConnectedUsers() { @@ -1283,31 +1239,16 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer NOISY_MESSAGE_INTERVAL_MSECS) { - static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex - ("Received a request for assignment type [^ ]+ from [^ ]+"); + static bool printedAssignmentTypeMessage = false; + if (!printedAssignmentTypeMessage && requestAssignment.getType() != Assignment::AgentType) { + printedAssignmentTypeMessage = true; qDebug() << "Received a request for assignment type" << requestAssignment.getType() << "from" << message->getSenderSockAddr(); - noisyMessageTimer.restart(); } SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); @@ -1341,13 +1282,11 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointergetUUID(), requestAssignment.getWalletUUID(), requestAssignment.getNodeVersion()); } else { - if (requestAssignment.getType() != Assignment::AgentType - || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) { - static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex - ("Unable to fulfill assignment request of type [^ ]+ from [^ ]+"); + static bool printedAssignmentRequestMessage = false; + if (!printedAssignmentRequestMessage && requestAssignment.getType() != Assignment::AgentType) { + printedAssignmentRequestMessage = true; qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() << "from" << message->getSenderSockAddr(); - noisyMessageTimer.restart(); } } } @@ -1593,10 +1532,12 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { callbackParameters.jsonCallbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleSuccessfulICEServerAddressUpdate"; - static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex - ("Updating ice-server address in High Fidelity Metaverse API to [^ \n]+"); - qDebug() << "Updating ice-server address in High Fidelity Metaverse API to" - << (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString()); + static bool printedIceServerMessage = false; + if (!printedIceServerMessage) { + printedIceServerMessage = true; + qDebug() << "Updating ice-server address in High Fidelity Metaverse API to" + << (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString()); + } static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address"; @@ -3482,4 +3423,4 @@ void DomainServer::handleOctreeFileReplacementRequest(QSharedPointergetCanReplaceContent()) { handleOctreeFileReplacement(message->readAll()); } -} \ No newline at end of file +} diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d98ac67dfd..ac9441319b 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -43,7 +43,6 @@ endif() list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC}) add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) - # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -181,8 +180,9 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () + if (BUILD_TOOLS AND NPM_EXECUTABLE) - # require JSDoc to be build before interface is deployed (Console Auto-complete) + # require JSDoc to be build before interface is deployed add_dependencies(resources jsdoc) endif() @@ -314,35 +314,55 @@ if (APPLE) ) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") - + set(RESOURCES_DEV_DIR "$/../Resources") # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" - "$/../Resources/scripts" + "${RESOURCES_DEV_DIR}/scripts" + ) + + # copy JSDoc files beside the executable + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" + "${RESOURCES_DEV_DIR}/jsdoc" ) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() else() + set(INTERFACE_EXEC_DIR "$") + set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources") + # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${RESOURCES_RCC}" - "$" + "${RESOURCES_RCC}" + "${INTERFACE_EXEC_DIR}" # FIXME, the edit script code loads HTML from the scripts folder # which in turn relies on CSS that refers to the fonts. In theory # we should be able to modify the CSS to reference the QRC path to # the ttf files, but doing so generates a CORS policy violation, # so we have to retain a copy of the fonts outside of the resources binary COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/resources/fonts" - "$/resources/fonts" + "${PROJECT_SOURCE_DIR}/resources/fonts" + "${RESOURCES_DEV_DIR}/fonts" COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_SOURCE_DIR}/scripts" - "$/scripts" + "${CMAKE_SOURCE_DIR}/scripts" + "${INTERFACE_EXEC_DIR}/scripts" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" + "${RESOURCES_DEV_DIR}/serverless/tutorial.json" + ) + + # copy JSDoc files beside the executable + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" + "${INTERFACE_EXEC_DIR}/jsdoc" ) # link target to external libraries @@ -368,7 +388,6 @@ else() endif() if (SCRIPTS_INSTALL_DIR) - # setup install of scripts beside interface executable install( DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/" @@ -377,6 +396,19 @@ if (SCRIPTS_INSTALL_DIR) ) endif() +if (DOWNLOAD_SERVERLESS_CONTENT) + add_dependency_external_projects(serverless-content) + + ExternalProject_Get_Property(serverless-content SOURCE_DIR) + + # for dev builds, copy the serverless content to the resources folder + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${SOURCE_DIR}" + "${RESOURCES_DEV_DIR}/serverless" + ) +endif () + if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml b/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml index e6dc03fa55..bf72869752 100644 --- a/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml @@ -1,6 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.0 import "../../qml/dialogs" diff --git a/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml b/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml index cb6552b075..55f7d27534 100644 --- a/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml +++ b/interface/resources/QtWebEngine/UIDelegates/FilePicker.qml @@ -1,6 +1,4 @@ import QtQuick 2.4 -import QtQuick.Dialogs 1.1 -import QtQuick.Controls 1.4 import "../../qml/dialogs" diff --git a/interface/resources/QtWebEngine/UIDelegates/Menu.qml b/interface/resources/QtWebEngine/UIDelegates/Menu.qml index 1bbbbd6cbe..46c00e758e 100644 --- a/interface/resources/QtWebEngine/UIDelegates/Menu.qml +++ b/interface/resources/QtWebEngine/UIDelegates/Menu.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 as Controls import "../../qml/controls-uit" import "../../qml/styles-uit" diff --git a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml index 1890fcb81d..6014b6834b 100644 --- a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml +++ b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml @@ -1,6 +1,4 @@ - import QtQuick 2.5 -import QtQuick.Controls 1.4 as Controls import "../../qml/controls-uit" import "../../qml/styles-uit" diff --git a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml index 01d3262bc0..e4ab3037ef 100644 --- a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml @@ -1,6 +1,4 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.0 import "../../qml/controls-uit" import "../../qml/styles-uit" diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index b3f16a115e..660bc281e3 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -1,15 +1,10 @@ { "name": "Keyboard/Mouse to Actions", "channels": [ - - { "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, - { "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.A", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.D", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" }, - { "from": "Keyboard.E", "when": "Keyboard.Shift", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.05 } ] }, - { "from": "Keyboard.C", "when": "Keyboard.Shift", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.05 } ] }, - { "from": "Keyboard.S", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, - { "from": "Keyboard.W", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" }, + { "from": "Keyboard.E", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.Q", "to": "Actions.LATERAL_LEFT" }, { "comment" : "Mouse turn need to be small continuous increments", @@ -44,9 +39,24 @@ ] }, + { "from": { "makeAxis" : [ - ["Keyboard.A", "Keyboard.Left" ], - ["Keyboard.D", "Keyboard.Right"] + ["Keyboard.Left" ], + ["Keyboard.Right"] + ] + }, + "when": ["Application.InHMD", "Application.SnapTurn", "!Keyboard.Shift"], + "to": "Actions.StepYaw", + "filters": + [ + { "type": "pulse", "interval": 0.5, "resetOnZero": true }, + { "type": "scale", "scale": 22.5 } + ] + }, + + { "from": { "makeAxis" : [ + ["Keyboard.A"], + ["Keyboard.D"] ] }, "when": [ "Application.InHMD", "Application.SnapTurn" ], @@ -59,26 +69,39 @@ }, { "from": { "makeAxis" : [ - ["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"], - ["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"] + ["Keyboard.Left"], + ["Keyboard.Right"] + ] + }, + "when": ["Application.CameraFirstPerson", "!Keyboard.Shift"], + "to": "Actions.Yaw" + }, + + { "from": { "makeAxis" : [ + ["Keyboard.Left"], + ["Keyboard.Right"] + ] + }, + "when": ["Application.CameraThirdPerson", "!Keyboard.Shift"], + "to": "Actions.Yaw" + }, + + { "from": { "makeAxis" : [ + ["Keyboard.A", "Keyboard.TouchpadLeft"], + ["Keyboard.D", "Keyboard.TouchpadRight"] ] }, "when": "Application.CameraFirstPerson", "to": "Actions.Yaw" }, { "from": { "makeAxis" : [ - ["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"], - ["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"] + ["Keyboard.A", "Keyboard.TouchpadLeft"], + ["Keyboard.D", "Keyboard.TouchpadRight"] ] }, "when": "Application.CameraThirdPerson", "to": "Actions.Yaw" }, - { "from": { "makeAxis" : [ ["Keyboard.A"], ["Keyboard.D"] ] }, - "when": "Application.CameraFSM", - "to": "Actions.Yaw" - }, - { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, "when": "Keyboard.RightMouseButton", "to": "Actions.Yaw", @@ -90,14 +113,10 @@ { "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" }, + { "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" }, { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, - { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, - { "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, - { "from": "Keyboard.Right", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, - { "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, - { "from": "Keyboard.Up", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" }, { "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" }, @@ -128,7 +147,7 @@ { "from": "Keyboard.MouseWheelLeft", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.02 } ]}, { "from": "Keyboard.MouseWheelRight", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.02 } ]}, - { "from": "Keyboard.Space", "to": "Actions.SHIFT" }, + { "from": "Keyboard.Space", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" }, { "from": "Keyboard.T", "to": "Actions.ACTION2" }, { "from": "Keyboard.Tab", "to": "Actions.ContextMenu" } diff --git a/interface/resources/controllers/touchscreenvirtualpad.json b/interface/resources/controllers/touchscreenvirtualpad.json index 907ff8b403..bae1172152 100644 --- a/interface/resources/controllers/touchscreenvirtualpad.json +++ b/interface/resources/controllers/touchscreenvirtualpad.json @@ -5,8 +5,25 @@ { "from": "TouchscreenVirtualPad.LX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, - { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": [ {"type": "deadZone", "min": 0.05} , "invert" ], "to": "Actions.Yaw" }, + { "from": "TouchscreenVirtualPad.JUMP_BUTTON_PRESS", "when": "!Application.CameraIndependent", "to": "Actions.VERTICAL_UP" }, + + { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", + "filters": [ + { "type": "deadZone", "min": 0.000 }, + { "type": "scale", "scale": 0.06 }, + "invert" + ], + "to": "Actions.Yaw" + }, + + { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", + "filters": [ + { "type": "deadZone", "min": 0.000 }, + { "type": "scale", "scale": 0.06 }, + "invert" + ], + "to": "Actions.Pitch" + } - { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "filters": [ {"type": "deadZone", "min": 0.05}, "invert" ], "to": "Actions.Pitch" } ] } diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.eot similarity index 82% rename from interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot rename to interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.eot index d3591e6499..1be5435ced 100644 Binary files a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot and b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.eot differ diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.svg similarity index 92% rename from interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg rename to interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.svg index 43c4932879..c68cb63a0d 100644 --- a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg +++ b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.svg @@ -25,7 +25,6 @@ - @@ -145,4 +144,14 @@ + + + + + + + + + + diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.ttf similarity index 82% rename from interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf rename to interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.ttf index 8907cf7858..edc447c132 100644 Binary files a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.woff new file mode 100644 index 0000000000..f26000caf4 Binary files /dev/null and b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.woff differ diff --git a/interface/resources/fonts/hifi-glyphs/icons-reference.html b/interface/resources/fonts/hifi-glyphs-1.31/icons-reference.html similarity index 93% rename from interface/resources/fonts/hifi-glyphs/icons-reference.html rename to interface/resources/fonts/hifi-glyphs-1.31/icons-reference.html index 2ca94d4110..b03f5214c9 100644 --- a/interface/resources/fonts/hifi-glyphs/icons-reference.html +++ b/interface/resources/fonts/hifi-glyphs-1.31/icons-reference.html @@ -12,7 +12,7 @@

HiFi Glyphs

-

This font was created for use inHigh Fidelity

+

This font was created for use in High Fidelity

CSS mapping

  • @@ -87,10 +87,6 @@
  • -
  • -
    - -
  • @@ -567,6 +563,46 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +

Character mapping

    @@ -642,10 +678,6 @@
    -
  • -
    - -
  • @@ -1122,6 +1154,46 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
+ * + * + * + * // Script file. + * var web3dOverlay = Overlays.addOverlay("web3d", { + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.5, z: -3 })), + * rotation: MyAvatar.orientation, + * url: Script.resolvePath("web3d.html"), + * alpha: 1.0 + * }); + * + * function onWebEventReceived(event) { + * print("onWebEventReceived() : " + JSON.stringify(event)); + * } + * + * overlayObject = Overlays.getOverlayObject(web3dOverlay); + * overlayObject.webEventReceived.connect(onWebEventReceived); + * + * Script.scriptEnding.connect(function () { + * Overlays.deleteOverlay(web3dOverlay); + * }); + */ + QObject* getOverlayObject(OverlayID id); + /**jsdoc * Get the ID of the 2D overlay at a particular point on the screen or HUD. * @function Overlays.getOverlayAtPoint @@ -680,6 +724,7 @@ private: unsigned int _stackOrder { 1 }; bool _enabled = true; + std::atomic _shuttingDown{ false }; PointerEvent calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray, RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType); diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index f4a9034187..2a583e0450 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -39,18 +39,20 @@ void QmlOverlay::buildQmlElement(const QUrl& url) { auto offscreenUi = DependencyManager::get(); offscreenUi->load(url, [=](QQmlContext* context, QObject* object) { - QQuickItem* rawPtr = dynamic_cast(object); - // Create a shared ptr with a custom deleter lambda, that calls deleteLater - _qmlElement = std::shared_ptr(rawPtr, [](QQuickItem* ptr) { - if (ptr) { - ptr->deleteLater(); - } - }); + _qmlElement = dynamic_cast(object); + connect(_qmlElement, &QObject::destroyed, this, &QmlOverlay::qmlElementDestroyed); }); } +void QmlOverlay::qmlElementDestroyed() { + _qmlElement = nullptr; +} + QmlOverlay::~QmlOverlay() { - _qmlElement.reset(); + if (_qmlElement) { + _qmlElement->deleteLater(); + } + _qmlElement = nullptr; } // QmlOverlay replaces Overlay's properties with those defined in the QML file used but keeps Overlay2D's properties. @@ -62,15 +64,13 @@ void QmlOverlay::setProperties(const QVariantMap& properties) { Overlay2D::setProperties(properties); auto bounds = _bounds; - std::weak_ptr weakQmlElement = _qmlElement; // check to see if qmlElement still exists - auto qmlElement = weakQmlElement.lock(); - if (qmlElement) { - qmlElement->setX(bounds.left()); - qmlElement->setY(bounds.top()); - qmlElement->setWidth(bounds.width()); - qmlElement->setHeight(bounds.height()); - QMetaObject::invokeMethod(qmlElement.get(), "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties)); + if (_qmlElement) { + _qmlElement->setX(bounds.left()); + _qmlElement->setY(bounds.top()); + _qmlElement->setWidth(bounds.width()); + _qmlElement->setHeight(bounds.height()); + QMetaObject::invokeMethod(_qmlElement, "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties)); } } diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h index ced2b6fa1f..7f2cf5a918 100644 --- a/interface/src/ui/overlays/QmlOverlay.h +++ b/interface/src/ui/overlays/QmlOverlay.h @@ -32,10 +32,11 @@ public: void render(RenderArgs* args) override; private: + Q_INVOKABLE void qmlElementDestroyed(); Q_INVOKABLE void buildQmlElement(const QUrl& url); protected: - std::shared_ptr _qmlElement; + QQuickItem* _qmlElement{ nullptr }; }; #endif // hifi_QmlOverlay_h diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index 9bb1da59d2..9c920efb93 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -20,7 +20,8 @@ #include const int FIXED_FONT_POINT_SIZE = 40; -const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 80.0f; // this is a ratio determined through experimentation +const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 92.0f; // Determined through experimentation to fit font to line + // height. const float LINE_SCALE_RATIO = 1.2f; QString const Text3DOverlay::TYPE = "text3d"; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 849ea5ee6b..dc004fe60d 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -882,6 +882,11 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { +#ifdef Q_OS_ANDROID + // disable IK on android + return underPoses; +#endif + // allows solutionSource to be overridden by an animVar auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 848f384687..0833b28142 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1246,6 +1246,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) { + const bool ENABLE_POLE_VECTORS = false; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; int hipsIndex = indexOfJoint("Hips"); @@ -1268,7 +1269,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - if (!leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); // smooth toward desired pole vector from previous pole vector... to reduce jitter @@ -1315,7 +1316,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - if (!rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { + if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); // smooth toward desired pole vector from previous pole vector... to reduce jitter @@ -1555,18 +1556,21 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateFeet(leftFootEnabled, rightFootEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]); + + if (headEnabled) { + // Blend IK chains toward the joint limit centers, this should stablize head and hand ik. + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); + } else { + // Blend IK chains toward the UnderPoses, so some of the animaton motion is present in the IK solution. + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); + } + // if the hips or the feet are being controlled. if (hipsEnabled || rightFootEnabled || leftFootEnabled) { - // for more predictable IK solve from the center of the joint limits, not from the underpose - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); - // replace the feet animation with the default pose, this is to prevent unexpected toe wiggling. _animVars.set("defaultPoseOverlayAlpha", 1.0f); _animVars.set("defaultPoseOverlayBoneSet", (int)AnimOverlay::BothFeetBoneSet); } else { - // augment the IK with the underPose. - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); - // feet should follow source animation _animVars.unset("defaultPoseOverlayAlpha"); _animVars.unset("defaultPoseOverlayBoneSet"); diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 59b3e874d7..518fdd3c17 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -39,9 +39,6 @@ AudioRingBufferTemplate::AudioRingBufferTemplate(int numFrameSamples, int num _nextOutput = _buffer; _endOfLastWrite = _buffer; } - - static QString repeatedOverflowMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); - static QString repeatedDroppedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); } template @@ -154,6 +151,11 @@ int AudioRingBufferTemplate::appendData(char *data, int maxSize) { return numReadSamples * SampleSize; } +namespace { + int repeatedOverflowMessageID = 0; + std::once_flag messageIDFlag; +} + template int AudioRingBufferTemplate::writeData(const char* data, int maxSize) { // only copy up to the number of samples we have capacity for @@ -167,7 +169,9 @@ int AudioRingBufferTemplate::writeData(const char* data, int maxSize) { _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; - qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); + std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); }, + &repeatedOverflowMessageID); + HIFI_FCDEBUG_ID(audio(), repeatedOverflowMessageID, RING_BUFFER_OVERFLOW_DEBUG); } if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { @@ -224,7 +228,7 @@ int AudioRingBufferTemplate::addSilentSamples(int silentSamples) { if (numWriteSamples > samplesRoomFor) { numWriteSamples = samplesRoomFor; - qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG); + HIFI_FCDEBUG(audio(), DROPPED_SILENT_DEBUG); } if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { @@ -275,7 +279,10 @@ int AudioRingBufferTemplate::writeSamples(ConstIterator source, int maxSample int samplesToDelete = samplesToCopy - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; - qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); + + std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); }, + &repeatedOverflowMessageID); + HIFI_FCDEBUG_ID(audio(), repeatedOverflowMessageID, RING_BUFFER_OVERFLOW_DEBUG); } Sample* bufferLast = _buffer + _bufferLength - 1; @@ -297,7 +304,10 @@ int AudioRingBufferTemplate::writeSamplesWithFade(ConstIterator source, int m int samplesToDelete = samplesToCopy - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; - qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); + + std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); }, + &repeatedOverflowMessageID); + HIFI_FCDEBUG_ID(audio(), repeatedOverflowMessageID, RING_BUFFER_OVERFLOW_DEBUG); } Sample* bufferLast = _buffer + _bufferLength - 1; diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index 49b34a894e..a6bbc71a65 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -80,10 +81,7 @@ int PositionalAudioStream::parsePositionalData(const QByteArray& positionalByteA // if the client sends us a bad position, flag it so that we don't consider this stream for mixing if (glm::isnan(_position.x) || glm::isnan(_position.y) || glm::isnan(_position.z)) { - static const QString INVALID_POSITION_REGEX = "PositionalAudioStream unpacked invalid position for node"; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(INVALID_POSITION_REGEX); - - qDebug() << "PositionalAudioStream unpacked invalid position for node" << uuidStringWithoutCurlyBraces(getNodeID()); + HIFI_FDEBUG("PositionalAudioStream unpacked invalid position for node" << uuidStringWithoutCurlyBraces(getNodeID()) ); _hasValidPosition = false; } else { diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index b25df633c0..428f86f0ab 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -58,6 +58,13 @@ void SkeletonModel::initJointStates() { glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(geometry, modelOffset); + { + // initialize _jointData with proper values for default joints + QVector defaultJointData; + _rig.copyJointsIntoJointData(defaultJointData); + _owningAvatar->setRawJointData(defaultJointData); + } + // Determine the default eye position for avatar scale = 1.0 int headJointIndex = geometry.headJointIndex; if (0 > headJointIndex || headJointIndex >= _rig.getJointStateCount()) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 1bbc8cc1a5..6b974ac9ae 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -559,7 +559,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - if (sendAll || last.rotationIsDefaultPose || last.rotation != data.rotation) { + bool mustSend = sendAll || last.rotationIsDefaultPose; + if (mustSend || last.rotation != data.rotation) { bool largeEnoughRotation = true; if (cullSmallChanges) { @@ -568,7 +569,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; } - if (sendAll || !cullSmallChanges || largeEnoughRotation) { + if (mustSend || !cullSmallChanges || largeEnoughRotation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG rotationSentCount++; @@ -608,10 +609,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float maxTranslationDimension = 0.0; for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData[i]; + const JointData& last = lastSentJointData[i]; if (!data.translationIsDefaultPose) { - if (sendAll || lastSentJointData[i].translation != data.translation) { - if (sendAll || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { + bool mustSend = sendAll || last.translationIsDefaultPose; + if (mustSend || last.translation != data.translation) { + if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG translationSentCount++; @@ -669,6 +672,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } if (sentJointDataOut) { + + // Mark default poses in lastSentJointData, so when they become non-default we send them. + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + JointData& local = localSentJointDataOut[i]; + if (data.rotationIsDefaultPose) { + local.rotationIsDefaultPose = true; + } + if (data.translationIsDefaultPose) { + local.translationIsDefaultPose = true; + } + } + // Push new sent joint data to sentJointDataOut sentJointDataOut->swap(localSentJointDataOut); } @@ -1816,13 +1832,13 @@ void AvatarData::setJointMappingsFromNetworkReply() { networkReply->deleteLater(); } -void AvatarData::sendAvatarDataPacket() { +void AvatarData::sendAvatarDataPacket(bool sendAll) { auto nodeList = DependencyManager::get(); // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed. // this is to guard against a joint moving once, the packet getting lost, and the joint never moving again. - bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); + bool cullSmallData = !sendAll && (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); auto dataDetail = cullSmallData ? SendAllData : CullSmallData; QByteArray avatarByteArray = toByteArrayStateful(dataDetail); @@ -2058,24 +2074,34 @@ static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); -static const int JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION = 0; -static const int JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION = 1; +enum class JsonAvatarFrameVersion : int { + JointRotationsInRelativeFrame = 0, + JointRotationsInAbsoluteFrame, + JointDefaultPoseBits +}; QJsonValue toJsonValue(const JointData& joint) { QJsonArray result; result.push_back(toJsonValue(joint.rotation)); result.push_back(toJsonValue(joint.translation)); + result.push_back(QJsonValue(joint.rotationIsDefaultPose)); + result.push_back(QJsonValue(joint.translationIsDefaultPose)); return result; } -JointData jointDataFromJsonValue(const QJsonValue& json) { +JointData jointDataFromJsonValue(int version, const QJsonValue& json) { JointData result; if (json.isArray()) { QJsonArray array = json.toArray(); result.rotation = quatFromJsonValue(array[0]); - result.rotationIsDefaultPose = false; result.translation = vec3FromJsonValue(array[1]); - result.translationIsDefaultPose = false; + if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) { + result.rotationIsDefaultPose = array[2].toBool(); + result.translationIsDefaultPose = array[3].toBool(); + } else { + result.rotationIsDefaultPose = false; + result.translationIsDefaultPose = false; + } } return result; } @@ -2083,7 +2109,7 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { QJsonObject AvatarData::toJson() const { QJsonObject root; - root[JSON_AVATAR_VERSION] = JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION; + root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits; if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); @@ -2158,7 +2184,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { version = json[JSON_AVATAR_VERSION].toInt(); } else { // initial data did not have a version field. - version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION; + version = (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame; } if (json.contains(JSON_AVATAR_BODY_MODEL)) { @@ -2235,7 +2261,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { // } if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { - if (version == JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION) { + if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) { // because we don't have the full joint hierarchy skeleton of the model, // we can't properly convert from relative rotations into absolute rotations. quint64 now = usecTimestampNow(); @@ -2247,7 +2273,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); jointArray.reserve(jointArrayJson.size()); for (const auto& jointJson : jointArrayJson) { - auto joint = jointDataFromJsonValue(jointJson); + auto joint = jointDataFromJsonValue(version, jointJson); jointArray.push_back(joint); } setRawJointData(jointArray); @@ -2558,4 +2584,4 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value[EntityID] = binaryEntityProperties; } -} \ No newline at end of file +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e927120b07..7c188019db 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -256,6 +256,11 @@ namespace AvatarDataPacket { SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes() uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows. SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed() + + SixByteQuat leftHandControllerRotation; + SixByteTrans leftHandControllerTranslation; + SixByteQuat rightHandControllerRotation; + SixByteTrans rightHandControllerTranslation; }; */ size_t maxJointDataSize(size_t numJoints); @@ -707,11 +712,11 @@ signals: void sessionUUIDChanged(); public slots: - void sendAvatarDataPacket(); + void sendAvatarDataPacket(bool sendAll = false); void sendIdentityPacket(); void setJointMappingsFromNetworkReply(); - void setSessionUUID(const QUuid& sessionUUID) { + virtual void setSessionUUID(const QUuid& sessionUUID) { if (sessionUUID != getID()) { if (sessionUUID == QUuid()) { setID(AVATAR_SELF_ID); diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index d8dd7f5e35..06a487d6ea 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -128,6 +128,7 @@ namespace controller { makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"), + makeButtonPair(Action::SPRINT, "Sprint"), makeAxisPair(Action::RETICLE_CLICK, "ReticleClick"), makeAxisPair(Action::RETICLE_X, "ReticleX"), @@ -183,6 +184,7 @@ namespace controller { makeButtonPair(Action::ACTION2, "ACTION2"), makeButtonPair(Action::CONTEXT_MENU, "CONTEXT_MENU"), makeButtonPair(Action::TOGGLE_MUTE, "TOGGLE_MUTE"), + makeButtonPair(Action::SPRINT, "SPRINT") }; return availableInputs; } diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index a133d62c9f..0c77d63863 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -174,6 +174,7 @@ enum class Action { TRACKED_OBJECT_13, TRACKED_OBJECT_14, TRACKED_OBJECT_15, + SPRINT, NUM_ACTIONS }; diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 2c60ca25f5..fbc87898a4 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -96,9 +96,9 @@ namespace controller { Q_INVOKABLE QObject* parseMapping(const QString& json); Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl); - Q_INVOKABLE const QVariantMap& getHardware() { return _hardware; } - Q_INVOKABLE const QVariantMap& getActions() { return _actions; } - Q_INVOKABLE const QVariantMap& getStandard() { return _standard; } + Q_INVOKABLE const QVariantMap getHardware() { return _hardware; } + Q_INVOKABLE const QVariantMap getActions() { return _actions; } + Q_INVOKABLE const QVariantMap getStandard() { return _standard; } Q_INVOKABLE void startInputRecording(); Q_INVOKABLE void stopInputRecording(); Q_INVOKABLE void startInputPlayback(); diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 29f011fba2..371deec7d5 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -73,7 +73,8 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID; - for (const auto& inputMapping : device->getAvailableInputs()) { + auto inputs = device->getAvailableInputs(); + for (const auto& inputMapping : inputs) { const auto& input = inputMapping.first; // Ignore aliases if (_endpointsByInput.count(input)) { @@ -126,7 +127,8 @@ void UserInputMapper::removeDevice(int deviceID) { _mappingsByDevice.erase(mappingsEntry); } - for (const auto& inputMapping : device->getAvailableInputs()) { + auto inputs = device->getAvailableInputs(); + for (const auto& inputMapping : inputs) { const auto& input = inputMapping.first; auto endpoint = _endpointsByInput.find(input); if (endpoint != _endpointsByInput.end()) { @@ -171,7 +173,7 @@ InputDevice::Pointer UserInputMapper::getDevice(const Input& input) { } } -QString UserInputMapper::getDeviceName(uint16 deviceID) { +QString UserInputMapper::getDeviceName(uint16 deviceID) { Locker locker(_lock); if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { return _registeredDevices[deviceID]->_name; @@ -181,7 +183,7 @@ QString UserInputMapper::getDeviceName(uint16 deviceID) { int UserInputMapper::findDevice(QString name) const { Locker locker(_lock); - for (auto device : _registeredDevices) { + for (const auto& device : _registeredDevices) { if (device.second->_name == name) { return device.first; } @@ -192,7 +194,7 @@ int UserInputMapper::findDevice(QString name) const { QVector UserInputMapper::getDeviceNames() { Locker locker(_lock); QVector result; - for (auto device : _registeredDevices) { + for (const auto& device : _registeredDevices) { QString deviceName = device.second->_name.split(" (")[0]; result << deviceName; } @@ -218,7 +220,7 @@ Input UserInputMapper::findDeviceInput(const QString& inputName) const { const auto& device = _registeredDevices.at(deviceID); auto deviceInputs = device->getAvailableInputs(); - for (auto input : deviceInputs) { + for (const auto& input : deviceInputs) { if (input.second == inputName) { return input.first; } @@ -321,7 +323,8 @@ QVector UserInputMapper::getAllActions() const { QString UserInputMapper::getActionName(Action action) const { Locker locker(_lock); - for (auto actionPair : getActionInputs()) { + auto inputs = getActionInputs(); + for (const auto& actionPair : inputs) { if (actionPair.first.channel == toInt(action)) { return actionPair.second; } @@ -331,18 +334,20 @@ QString UserInputMapper::getActionName(Action action) const { QString UserInputMapper::getStandardPoseName(uint16_t pose) { Locker locker(_lock); - for (auto posePair : getStandardInputs()) { + auto inputs = getStandardInputs(); + for (const auto& posePair : inputs) { if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) { return posePair.second; } } return QString(); -} +} QVector UserInputMapper::getActionNames() const { Locker locker(_lock); QVector result; - for (auto actionPair : getActionInputs()) { + auto inputs = getActionInputs(); + for (const auto& actionPair : inputs) { result << actionPair.second; } return result; @@ -357,7 +362,7 @@ Pose UserInputMapper::getPoseState(Action action) const { bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = false; - for (auto device : _registeredDevices) { + for (const auto& device : _registeredDevices) { toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand); } return toReturn; @@ -469,7 +474,7 @@ void UserInputMapper::runMappings() { if (debugRoutes) { qCDebug(controllers) << "Beginning mapping frame"; } - for (auto endpointEntry : this->_endpointsByInput) { + for (const auto& endpointEntry : _endpointsByInput) { endpointEntry.second->reset(); } @@ -542,9 +547,9 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } - // Most endpoints can only be read once (though a given mapping can route them to + // Most endpoints can only be read once (though a given mapping can route them to // multiple places). Consider... If the default is to wire the A button to JUMP - // and someone else wires it to CONTEXT_MENU, I don't want both to occur when + // and someone else wires it to CONTEXT_MENU, I don't want both to occur when // I press the button. The exception is if I'm wiring a control back to itself // in order to adjust my interface, like inverting the Y axis on an analog stick if (!route->peek && !source->readable()) { @@ -897,7 +902,8 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) if (value.isArray()) { // Support "when" : [ "GamePad.RB", "GamePad.LB" ] Conditional::List children; - for (auto arrayItem : value.toArray()) { + auto array = value.toArray(); + for (const auto& arrayItem : array) { Conditional::Pointer childConditional = parseConditional(arrayItem); if (!childConditional) { return Conditional::Pointer(); @@ -908,7 +914,7 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) } else if (value.isString()) { // Support "when" : "GamePad.RB" auto conditionalToken = value.toString(); - + // Detect for modifier case (Not...) QString conditionalModifier; const QString JSON_CONDITIONAL_MODIFIER_NOT("!"); @@ -943,12 +949,12 @@ Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) { result = Filter::getFactory().create(value.toString()); } else if (value.isObject()) { result = Filter::parse(value.toObject()); - } + } if (!result) { qWarning() << "Invalid filter definition " << value; } - + return result; } @@ -960,7 +966,7 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { if (value.isArray()) { Filter::List result; auto filtersArray = value.toArray(); - for (auto filterValue : filtersArray) { + for (const auto& filterValue : filtersArray) { Filter::Pointer filter = parseFilter(filterValue); if (!filter) { return Filter::List(); @@ -968,7 +974,7 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { result.push_back(filter); } return result; - } + } Filter::Pointer filter = parseFilter(value); if (!filter) { @@ -980,7 +986,8 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { if (value.isArray()) { ArrayEndpoint::Pointer result = std::make_shared(); - for (auto arrayItem : value.toArray()) { + auto array = value.toArray(); + for (const auto& arrayItem : array) { Endpoint::Pointer destination = parseEndpoint(arrayItem); if (!destination) { return Endpoint::Pointer(); @@ -988,14 +995,14 @@ Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { result->_children.push_back(destination); } return result; - } - + } + return parseEndpoint(value); } Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { if (value.isObject()) { - auto object = value.toObject(); + auto object = value.toObject(); if (object.contains("makeAxis")) { auto axisValue = object.value("makeAxis"); if (axisValue.isArray()) { @@ -1017,7 +1024,8 @@ Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { Endpoint::Pointer UserInputMapper::parseAny(const QJsonValue& value) { if (value.isArray()) { Endpoint::List children; - for (auto arrayItem : value.toArray()) { + auto array = value.toArray(); + for (const auto& arrayItem : array) { Endpoint::Pointer destination = parseEndpoint(arrayItem); if (!destination) { return Endpoint::Pointer(); @@ -1162,7 +1170,7 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) { template bool hasDebuggableRoute(const T& routes) { - for (auto route : routes) { + for (const auto& route : routes) { if (route->debug) { return true; } @@ -1174,7 +1182,7 @@ bool hasDebuggableRoute(const T& routes) { void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) { Locker locker(_lock); // New routes for a device get injected IN FRONT of existing routes. Routes - // are processed in order so this ensures that the standard -> action processing + // are processed in order so this ensures that the standard -> action processing // takes place after all of the hardware -> standard or hardware -> action processing // because standard -> action is the first set of routes added. Route::List standardRoutes = mapping->routes; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp index ef9f04402b..f73268b41f 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -16,11 +16,14 @@ using namespace controller; void ActionEndpoint::apply(float newValue, const Pointer& source) { - InputRecorder* inputRecorder = InputRecorder::getInstance(); auto userInputMapper = DependencyManager::get(); - QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); - if(inputRecorder->isPlayingback()) { - newValue = inputRecorder->getActionState(actionName); + InputRecorder* inputRecorder = InputRecorder::getInstance(); + QString actionName; + if (inputRecorder->isPlayingback() || inputRecorder->isRecording()) { + actionName = userInputMapper->getActionName(Action(_input.getChannel())); + if (inputRecorder->isPlayingback()) { + newValue = inputRecorder->getActionState(actionName); + } } _currentValue += newValue; @@ -32,10 +35,12 @@ void ActionEndpoint::apply(float newValue, const Pointer& source) { void ActionEndpoint::apply(const Pose& value, const Pointer& source) { _currentPose = value; - InputRecorder* inputRecorder = InputRecorder::getInstance(); auto userInputMapper = DependencyManager::get(); - QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); - inputRecorder->setActionState(actionName, _currentPose); + InputRecorder* inputRecorder = InputRecorder::getInstance(); + if (inputRecorder->isRecording()) { + QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); + inputRecorder->setActionState(actionName, _currentPose); + } if (!_currentPose.isValid()) { return; diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp index 7f0e80cbae..2f20cd82c6 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp @@ -6,10 +6,43 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// NOTE: we don't need to include this header unless/until we add additional symbols. -// By removing this header we prevent these warnings on windows: -// -// warning LNK4221: This object file does not define any previously undefined public symbols, -// so it will not be used by any link operation that consumes this library -// -//#include "JSEndpoint.h" \ No newline at end of file +#include "JSEndpoint.h" +#include "../../Logging.h" + +using namespace controller; + +QString formatException(const QJSValue& exception) { + QString note { "UncaughtException" }; + QString result; + + const auto message = exception.toString(); + const auto fileName = exception.property("fileName").toString(); + const auto lineNumber = exception.property("lineNumber").toString(); + const auto stacktrace = exception.property("stack").toString(); + + const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3"; + const QString SCRIPT_BACKTRACE_SEP = "\n "; + + result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); + if (!stacktrace.isEmpty()) { + result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); + } + return result; +} + +float JSEndpoint::peek() const { + QJSValue result = _callable.call(); + if (result.isError()) { + qCDebug(controllers).noquote() << formatException(result); + return 0.0f; + } else { + return (float)result.toNumber(); + } +} + +void JSEndpoint::apply(float newValue, const Pointer& source) { + QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) })); + if (result.isError()) { + qCDebug(controllers).noquote() << formatException(result); + } +} diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h index 24d5ec93e9..4d179da8e6 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h @@ -24,16 +24,11 @@ public: : Endpoint(Input::INVALID_INPUT), _callable(callable) { } - virtual float peek() const override { - return (float)const_cast(this)->_callable.call().toNumber(); - } - - virtual void apply(float newValue, const Pointer& source) override { - _callable.call(QJSValueList({ QJSValue(newValue) })); - } + virtual float peek() const override; + virtual void apply(float newValue, const Pointer& source) override; private: - QJSValue _callable; + mutable QJSValue _callable; }; } diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp index 3e7fde347e..e2c48d776f 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -7,6 +7,7 @@ // #include "ScriptEndpoint.h" +#include "../../Logging.h" #include @@ -14,6 +15,25 @@ using namespace controller; +QString formatException(const QScriptValue& exception) { + QString note { "UncaughtException" }; + QString result; + + const auto message = exception.toString(); + const auto fileName = exception.property("fileName").toString(); + const auto lineNumber = exception.property("lineNumber").toString(); + const auto stacktrace = exception.property("stack").toString(); + + const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3"; + const QString SCRIPT_BACKTRACE_SEP = "\n "; + + result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); + if (!stacktrace.isEmpty()) { + result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); + } + return result; +} + float ScriptEndpoint::peek() const { const_cast(this)->updateValue(); return _lastValueRead; @@ -26,10 +46,11 @@ void ScriptEndpoint::updateValue() { } QScriptValue result = _callable.call(); - - // If the callable ever returns a non-number, we assume it's a pose - // and start reporting ourselves as a pose. - if (result.isNumber()) { + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + _lastValueRead = 0.0f; + } else if (result.isNumber()) { _lastValueRead = (float)_callable.call().toNumber(); } else { Pose::fromScriptValue(result, _lastPoseRead); @@ -52,8 +73,12 @@ void ScriptEndpoint::internalApply(float value, int sourceID) { Q_ARG(int, sourceID)); return; } - _callable.call(QScriptValue(), + QScriptValue result = _callable.call(QScriptValue(), QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) })); + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + } } Pose ScriptEndpoint::peekPose() const { @@ -67,6 +92,10 @@ void ScriptEndpoint::updatePose() { return; } QScriptValue result = _callable.call(); + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + } Pose::fromScriptValue(result, _lastPoseRead); } @@ -85,6 +114,10 @@ void ScriptEndpoint::internalApply(const Pose& newPose, int sourceID) { Q_ARG(int, sourceID)); return; } - _callable.call(QScriptValue(), + QScriptValue result = _callable.call(QScriptValue(), QScriptValueList({ Pose::toScriptValue(_callable.engine(), newPose), QScriptValue(sourceID) })); + if (result.isError()) { + // print JavaScript exception + qCDebug(controllers).noquote() << formatException(result); + } } diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index f260fa959f..392fa7e2a2 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -12,6 +12,7 @@ #include +// These properties have JSDoc documentation in HMDScriptingInterface.h. class AbstractHMDScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(bool active READ isHMDMode) @@ -30,7 +31,27 @@ public: bool isHMDMode() const; signals: + /**jsdoc + * Triggered when the HMD.ipdScale property value changes. + * @function HMD.IPDScaleChanged + * @returns {Signal} + */ void IPDScaleChanged(); + + /**jsdoc + * Triggered when Interface's display mode changes and when the user puts on or takes off their HMD. + * @function HMD.displayModeChanged + * @param {boolean} isHMDMode - true if the display mode is HMD, otherwise false. This is the + * same value as provided by HMD.active. + * @returns {Signal} + * @example Report when the display mode changes. + * HMD.displayModeChanged.connect(function (isHMDMode) { + * print("Display mode changed"); + * print("isHMD = " + isHMDMode); + * print("HMD.active = " + HMD.active); + * print("HMD.mounted = " + HMD.mounted); + * }); + */ void displayModeChanged(bool isHMDMode); private: diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 97f74fa24e..f33af1b580 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -25,11 +25,11 @@ static const QString FULLSCREEN = "Fullscreen"; void Basic2DWindowOpenGLDisplayPlugin::customizeContext() { #if defined(Q_OS_ANDROID) + qreal dpi = getFullscreenTarget()->physicalDotsPerInch(); + _virtualPadPixelSize = dpi * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI; + auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png"; auto image = QImage(iconPath); - qreal dpi = getFullscreenTarget()->physicalDotsPerInch(); - _virtualPadPixelSize = dpi * VirtualPad::Manager::PIXEL_SIZE / VirtualPad::Manager::DPI; - if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } @@ -69,6 +69,29 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() { _virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits()); _virtualPadStickBaseTexture->setAutoGenerateMips(true); } + + _virtualPadJumpBtnPixelSize = dpi * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI; + iconPath = PathUtils::resourcesPath() + "images/fly.png"; + image = QImage(iconPath); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + if ((image.width() > 0) && (image.height() > 0)) { + image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio); + image = image.mirrored(); + + _virtualPadJumpBtnTexture = gpu::Texture::createStrict( + gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), + image.width(), image.height(), + gpu::Texture::MAX_NUM_MIPS, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + _virtualPadJumpBtnTexture->setSource("virtualPad jump"); + auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha(); + _virtualPadJumpBtnTexture->setUsage(usage.build()); + _virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); + _virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits()); + _virtualPadJumpBtnTexture->setAutoGenerateMips(true); + } #endif Parent::customizeContext(); } @@ -124,6 +147,20 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); }); + + // render stick head + auto jumpTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(), + _virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize); + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_cursorPipeline); + batch.setResourceTexture(0, _virtualPadJumpBtnTexture); + batch.resetViewTransform(); + batch.setModelTransform(jumpTransform); + batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); } #endif Parent::compositeExtra(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 26c48743b7..a061a4c923 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -46,5 +46,8 @@ private: gpu::TexturePointer _virtualPadStickTexture; gpu::TexturePointer _virtualPadStickBaseTexture; qreal _virtualPadPixelSize; + + gpu::TexturePointer _virtualPadJumpBtnTexture; + qreal _virtualPadJumpBtnPixelSize; #endif }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 9597be7e53..4b33565bfd 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -530,7 +530,11 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur batch.setStateScissorRect(scissor); batch.setViewportTransform(viewport); batch.setResourceTexture(0, texture); +#ifndef USE_GLES batch.setPipeline(_presentPipeline); +#else + batch.setPipeline(_simplePipeline); +#endif batch.draw(gpu::TRIANGLE_STRIP, 4); if (copyFbo) { gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight()); diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index cfdfb1fc21..cb69d3a514 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -56,10 +56,8 @@ glm::mat4 StereoDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& basePr static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; -std::vector _screenActions; bool StereoDisplayPlugin::internalActivate() { auto screens = qApp->screens(); - _screenActions.resize(screens.size()); for (int i = 0; i < screens.size(); ++i) { auto screen = screens.at(i); QString name = QString("Screen %1: %2").arg(i + 1).arg(screen->name()); @@ -67,9 +65,9 @@ bool StereoDisplayPlugin::internalActivate() { if (screen == qApp->primaryScreen()) { checked = true; } - auto action = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name, - [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); - _screenActions[i] = action; + const uint32_t screenIndex = i; + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name, + [=](bool clicked) { updateScreen(screenIndex); }, true, checked, "Screens"); } _container->removeMenu(FRAMERATE); @@ -80,18 +78,12 @@ bool StereoDisplayPlugin::internalActivate() { return Parent::internalActivate(); } -void StereoDisplayPlugin::updateScreen() { - for (uint32_t i = 0; i < _screenActions.size(); ++i) { - if (_screenActions[i]->isChecked()) { - _screen = qApp->screens().at(i); - _container->setFullscreen(_screen); - break; - } - } +void StereoDisplayPlugin::updateScreen(uint32_t i) { + _screen = qApp->screens().at(i); + _container->setFullscreen(_screen); } void StereoDisplayPlugin::internalDeactivate() { - _screenActions.clear(); _container->unsetFullscreen(); Parent::internalDeactivate(); } diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index c4205ea1db..5a7ca24059 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -31,7 +31,7 @@ public: protected: virtual bool internalActivate() override; virtual void internalDeactivate() override; - void updateScreen(); + void updateScreen(uint32_t i); float _ipd{ 0.064f }; QScreen* _screen; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index c33b87e5cf..b61f24972a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -135,10 +135,8 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St template std::shared_ptr make_renderer(const EntityItemPointer& entity) { - T* rawResult = new T(entity); - // We want to use deleteLater so that renderer destruction gets pushed to the main thread - return std::shared_ptr(rawResult, std::bind(&QObject::deleteLater, rawResult)); + return std::shared_ptr(new T(entity), [](T* ptr) { ptr->deleteLater(); }); } EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) { diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index ea8edb8a08..7cea841bf0 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -18,7 +18,7 @@ bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityP if (entity->getMaterial() != _drawMaterial) { return true; } - if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) { + if (entity->getParentID() != _parentID) { return true; } if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) { @@ -31,8 +31,6 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& withWriteLock([&] { _drawMaterial = entity->getMaterial(); _parentID = entity->getParentID(); - _clientOnly = entity->getClientOnly(); - _owningAvatarID = entity->getOwningAvatarID(); _materialMappingPos = entity->getMaterialMappingPos(); _materialMappingScale = entity->getMaterialMappingScale(); _materialMappingRot = entity->getMaterialMappingRot(); @@ -102,7 +100,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { graphics::MaterialPointer drawMaterial; Transform textureTransform; withReadLock([&] { - parentID = _clientOnly ? _owningAvatarID : _parentID; + parentID = _parentID; renderTransform = _renderTransform; drawMaterial = _drawMaterial; textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0)); diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h index 8de2190a0c..96c720f79f 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h @@ -32,8 +32,6 @@ private: ShapeKey getShapeKey() override; QUuid _parentID; - bool _clientOnly; - QUuid _owningAvatarID; glm::vec2 _materialMappingPos; glm::vec2 _materialMappingScale; float _materialMappingRot; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index b05854da4e..1d34837a58 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -18,6 +18,9 @@ #include "render-utils/simple_vert.h" #include "render-utils/simple_frag.h" +#include "render-utils/simple_transparent_frag.h" +#include "render-utils/forward_simple_frag.h" +#include "render-utils/forward_simple_transparent_frag.h" #include "RenderPipelines.h" @@ -35,13 +38,23 @@ static const float SPHERE_ENTITY_SCALE = 0.5f; ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _procedural._vertexSource = simple_vert::getSource(); - _procedural._fragmentSource = simple_frag::getSource(); + // FIXME: Setup proper uniform slots and use correct pipelines for forward rendering + _procedural._opaquefragmentSource = simple_frag::getSource(); + // FIXME: Transparent procedural entities only seem to work if they use the opaque pipelines + //_procedural._transparentfragmentSource = simple_transparent_frag::getSource(); + _procedural._transparentfragmentSource = simple_frag::getSource(); _procedural._opaqueState->setCullMode(gpu::State::CULL_NONE); _procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); _procedural._opaqueState->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _procedural._transparentState->setCullMode(gpu::State::CULL_BACK); + _procedural._transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMask(*_procedural._transparentState); + _procedural._transparentState->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } bool ShapeEntityRenderer::needsRenderUpdate() const { @@ -131,6 +144,8 @@ ItemKey ShapeEntityRenderer::getKey() { withReadLock([&] { if (isTransparent()) { builder.withTransparent(); + } else if (_canCastShadow) { + builder.withShadowCaster(); } }); @@ -216,9 +231,9 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { if (mat) { outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity()); if (_procedural.isReady()) { - _procedural.prepare(batch, _position, _dimensions, _orientation); outColor = _procedural.getColor(outColor); outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; + _procedural.prepare(batch, _position, _dimensions, _orientation, outColor); proceduralRender = true; } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index bd00ded12d..f31ed4e238 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -34,7 +34,7 @@ static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount{ 0 }; -// Don't allow more than 100 concurrent web views +// Don't allow more than 20 concurrent web views static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; @@ -88,8 +88,14 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe return true; } - if (uvec2(getWindowSize(entity)) != toGlm(_webSurface->size())) { - return true; + { + QSharedPointer webSurface; + withReadLock([&] { + webSurface = _webSurface; + }); + if (webSurface && uvec2(getWindowSize(entity)) != toGlm(webSurface->size())) { + return true; + } } if (_lastSourceUrl != entity->getSourceUrl()) { @@ -108,9 +114,15 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } bool WebEntityRenderer::needsRenderUpdate() const { - if (!_webSurface) { - // If we have rendered recently, and there is no web surface, we're going to create one - return true; + { + QSharedPointer webSurface; + withReadLock([&] { + webSurface = _webSurface; + }); + if (!webSurface) { + // If we have rendered recently, and there is no web surface, we're going to create one + return true; + } } return Parent::needsRenderUpdate(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5d056e17d8..2e3a6c9552 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -62,7 +62,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : EntityItem::~EntityItem() { // these pointers MUST be correct at delete, else we probably have a dangling backpointer // to this EntityItem in the corresponding data structure. - assert(!_simulated); + assert(!_simulated || (!_element && !_physicsInfo)); assert(!_element); assert(!_physicsInfo); } @@ -693,7 +693,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // the entity-server is awarding us ownership which is what we want _simulationOwner.set(newSimOwner); } - } else if (newSimOwner.matchesValidID(myNodeID) && !_hasBidOnSimulation) { + } else if (newSimOwner.matchesValidID(myNodeID) && !_simulationOwner.pendingTake(now)) { // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, // this could happen when the user reloads the cache and entity tree. markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); @@ -1911,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } -void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { +void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) { if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) { qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; } @@ -1942,14 +1942,10 @@ void EntityItem::clearSimulationOwnership() { } -void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) { +void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) { _simulationOwner.setPendingPriority(priority, timestamp); } -void EntityItem::rememberHasSimulationOwnershipBid() const { - _hasBidOnSimulation = true; -} - QString EntityItem::actionsToDebugString() { QString result; QVector serializedActions; @@ -2197,10 +2193,8 @@ void EntityItem::deserializeActionsInternal() { entity->addActionInternal(simulation, action); updated << actionID; } else { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex(".*action creation failed for.*"); - qCDebug(entities) << "EntityItem::deserializeActionsInternal -- action creation failed for" - << getID() << _name; // getName(); + HIFI_FCDEBUG(entities(), "EntityItem::deserializeActionsInternal -- action creation failed for" + << getID() << _name); // getName(); removeActionInternal(actionID, nullptr); } } @@ -2962,13 +2956,6 @@ void EntityItem::retrieveMarketplacePublicKey() { } void EntityItem::preDelete() { - // clear out any left-over actions - EntityTreeElementPointer element = _element; // use local copy of _element for logic below - EntityTreePointer entityTree = element ? element->getTree() : nullptr; - EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; - if (simulation) { - clearActions(simulation); - } } void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0303964e18..0557bbe5ad 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -58,6 +58,9 @@ using EntityTreeElementExtraEncodeDataPointer = std::shared_ptrgetLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, @@ -304,14 +306,14 @@ public: // FIXME not thread safe? const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } - void setSimulationOwner(const QUuid& id, quint8 priority); + void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const SimulationOwner& owner); - void promoteSimulationPriority(quint8 priority); + void promoteSimulationPriority(uint8_t priority); - quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); } + uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); } QUuid getSimulatorID() const { return _simulationOwner.getID(); } void clearSimulationOwnership(); - void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); + void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp); uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } void rememberHasSimulationOwnershipBid() const; @@ -489,6 +491,9 @@ public: void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); std::unordered_map getMaterials(); + void setSimulationOwnershipExpiry(uint64_t expiry) { _simulationOwnershipExpiry = expiry; } + uint64_t getSimulationOwnershipExpiry() const { return _simulationOwnershipExpiry; } + signals: void requestRenderUpdate(); @@ -619,9 +624,6 @@ protected: static quint64 _rememberDeletedActionTime; mutable QHash _previouslyDeletedActions; - // per entity keep state if it ever bid on simulation, so that we can ignore false simulation ownership - mutable bool _hasBidOnSimulation { false }; - QUuid _sourceUUID; /// the server node UUID we came from bool _clientOnly { false }; @@ -642,6 +644,7 @@ protected: quint64 _lastUpdatedAngularVelocityTimestamp { 0 }; quint64 _lastUpdatedAccelerationTimestamp { 0 }; quint64 _lastUpdatedQueryAACubeTimestamp { 0 }; + uint64_t _simulationOwnershipExpiry { 0 }; bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 828430e2ca..f78168b05d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -363,6 +363,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -451,8 +452,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.EntityType} type - The entity type. You cannot change the type of an entity after it's created. (Though * its value may switch among "Box", "Shape", and "Sphere" depending on changes to * the shape property set for entities of these types.) Read-only. - * @property {boolean} clientOnly=false - If true then the entity is an avatar entity, otherwise it is a server - * entity. Read-only. + * @property {boolean} clientOnly=false - If true then the entity is an avatar entity; otherwise it is a server + * entity. An avatar entity follows you to each domain you visit, rendering at the same world coordinates unless it's + * parented to your avatar. Value cannot be changed after the entity is created.
+ * The value can also be set at entity creation by using the clientOnly parameter in + * {@link Entities.addEntity}. * @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if clientOnly is * true, otherwise {@link Uuid|Uuid.NULL}. Read-only. * @@ -473,18 +477,19 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {boolean} visible=true - Whether or not the entity is rendered. If true then the entity is rendered. * @property {boolean} canCastShadows=true - Whether or not the entity casts shadows. Currently applicable only to * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a - * {@link Entities.EntityType|Zone} entity with castShadows enabled in its {@link Entities.EntityProperties-Zone|keyLight} property. + * {@link Entities.EntityType|Zone} entity with castShadows enabled in its + * {@link Entities.EntityProperties-Zone|keyLight} property. * * @property {Vec3} position=0,0,0 - The position of the entity. * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. * @property {Vec3} registrationPoint=0.5,0.5,0.5 - The point in the entity that is set to the entity's position and is rotated - * about, {@link Vec3|Vec3.ZERO} – {@link Vec3|Vec3.ONE}. A value of {@link Vec3|Vec3.ZERO} is the entity's - * minimum x, y, z corner; a value of {@link Vec3|Vec3.ONE} is the entity's maximum x, y, z corner. + * about, {@link Vec3(0)|Vec3.ZERO} – {@link Vec3(0)|Vec3.ONE}. A value of {@link Vec3(0)|Vec3.ZERO} is the entity's + * minimum x, y, z corner; a value of {@link Vec3(0)|Vec3.ONE} is the entity's maximum x, y, z corner. * * @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise - * {@link Vec3|Vec3.ZERO}. Read-only. + * {@link Vec3(0)|Vec3.ZERO}. Read-only. * @property {Vec3} naturalDimensions - The dimensions of the entity's unscaled mesh model if it has one, otherwise - * {@link Vec3|Vec3.ONE}. Read-only. + * {@link Vec3(0)|Vec3.ONE}. Read-only. * * @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates. * @property {number} damping=0.39347 - How much to slow down the linear velocity of an entity over time, 0.0 @@ -501,13 +506,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} gravity=0,0,0 - The acceleration due to gravity in m/s2 that the entity should move with, in * world coordinates. Set to { x: 0, y: -9.8, z: 0 } to simulate Earth's gravity. Gravity is applied to an * entity's motion only if its dynamic property is true. If changing an entity's - * gravity from {@link Vec3|Vec3.ZERO}, you need to give it a small velocity in order to kick off - * physics simulation. + * gravity from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small velocity in order to kick + * off physics simulation. * The gravity value is applied in addition to the acceleration value. * @property {Vec3} acceleration=0,0,0 - A general acceleration in m/s2 that the entity should move with, in world * coordinates. The acceleration is applied to an entity's motion only if its dynamic property is - * true. If changing an entity's acceleration from {@link Vec3|Vec3.ZERO}, you need to give it a - * small velocity in order to kick off physics simulation. + * true. If changing an entity's acceleration from {@link Vec3(0)|Vec3.ZERO}, you need to give it + * a small velocity in order to kick off physics simulation. * The acceleration value is applied in addition to the gravity value. * @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, 0.0 – * 0.99. The higher the value, the more bouncy. @@ -1205,6 +1210,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); // Certifiable Properties COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_NAME, itemName); @@ -1249,7 +1255,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_SPREAD, colorSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_START, colorStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_FINISH, colorFinish); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_SPREAD, alphaSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); @@ -1373,6 +1378,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_DATA, materialData); } /**jsdoc @@ -1413,7 +1419,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable // Rendering info @@ -1536,6 +1542,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1899,6 +1906,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString); // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); @@ -2295,6 +2303,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, properties.getMaterialMappingPos()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, properties.getMaterialMappingScale()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, properties.getMaterialMappingRot()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, properties.getMaterialData()); } APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); @@ -2663,6 +2672,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_DATA, QString, setMaterialData); } READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); @@ -2846,6 +2856,7 @@ void EntityItemProperties::markAllChanged() { _materialMappingPosChanged = true; _materialMappingScaleChanged = true; _materialMappingRotChanged = true; + _materialDataChanged = true; // Certifiable Properties _itemNameChanged = true; @@ -3197,6 +3208,9 @@ QList EntityItemProperties::listChangedProperties() { if (materialMappingRotChanged()) { out += "materialMappingRot"; } + if (materialDataChanged()) { + out += "materialData"; + } // Certifiable Properties if (itemNameChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 9e5d6ddc79..38e4f0c8c0 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -230,6 +230,7 @@ public: DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0)); DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1)); DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); + DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, ""); // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); @@ -326,7 +327,7 @@ public: void clearSimulationOwner(); void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const QByteArray& data); - void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); } + void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); } void setActionDataDirty() { _actionDataChanged = true; } @@ -502,6 +503,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalPosition, localPosition, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalRotation, localRotation, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalVelocity, localVelocity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalAngularVelocity, localAngularVelocity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalDimensions, localDimensions, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 07908fe6cf..99a5f287ea 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -240,6 +240,7 @@ enum EntityPropertyList { PROP_MATERIAL_MAPPING_POS, PROP_MATERIAL_MAPPING_SCALE, PROP_MATERIAL_MAPPING_ROT, + PROP_MATERIAL_DATA, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 7e15e78624..0eec5cf04b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -241,30 +241,23 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _activityTracking.addedEntityCount++; + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); + EntityItemProperties propertiesWithSimID = properties; if (clientOnly) { - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); + const QUuid myNodeID = sessionID; propertiesWithSimID.setClientOnly(clientOnly); propertiesWithSimID.setOwningAvatarID(myNodeID); } + propertiesWithSimID.setLastEditedBy(sessionID); + bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); - auto dimensions = propertiesWithSimID.getDimensions(); - float volume = dimensions.x * dimensions.y * dimensions.z; - auto density = propertiesWithSimID.getDensity(); - auto newVelocity = propertiesWithSimID.getVelocity().length(); - float cost = calculateCost(density * volume, 0, newVelocity); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - return QUuid(); - } - EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. @@ -295,9 +288,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties // queue the packet if (success) { - emit debitEnergySource(cost); queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); - return id; } else { return QUuid(); @@ -321,6 +312,11 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin if (!textures.isEmpty()) { properties.setTextures(textures); } + + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); + properties.setLastEditedBy(sessionID); + return addEntity(properties); } @@ -376,29 +372,15 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& _activityTracking.editedEntityCount++; - EntityItemProperties properties = scriptSideProperties; + auto nodeList = DependencyManager::get(); + auto sessionID = nodeList->getSessionUUID(); - auto dimensions = properties.getDimensions(); - float volume = dimensions.x * dimensions.y * dimensions.z; - auto density = properties.getDensity(); - auto newVelocity = properties.getVelocity().length(); - float oldVelocity = { 0.0f }; + EntityItemProperties properties = scriptSideProperties; + properties.setLastEditedBy(sessionID); EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); - - //if there is no local entity entity tree, no existing velocity, use 0. - float cost = calculateCost(density * volume, oldVelocity, newVelocity); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - return QUuid(); - } else { - //debit the avatar energy and continue - emit debitEnergySource(cost); - } - return id; } // If we have a local entity tree set, then also update it. @@ -410,7 +392,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& return; } - auto nodeList = DependencyManager::get(); if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { // don't edit other avatar's avatarEntities return; @@ -420,9 +401,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. - //existing entity, retrieve old velocity for check down below - oldVelocity = entity->getWorldVelocity().length(); - if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } @@ -442,23 +420,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); - - float cost = calculateCost(density * volume, oldVelocity, newVelocity); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - updatedEntity = false; - } else { - //debit the avatar energy and continue - updatedEntity = _entityTree->updateEntity(entityID, properties); - if (updatedEntity) { - emit debitEnergySource(cost); - } - } + updatedEntity = _entityTree->updateEntity(entityID, properties); }); // FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially - // breaks avatar energy and entities that are parented. + // breaks entities that are parented. // // To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist // in the local entity tree, we need to allow those edits to go through to the server. @@ -500,7 +466,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); - entity->rememberHasSimulationOwnershipBid(); } } if (properties.queryAACubeRelatedPropertyChanged()) { @@ -523,6 +488,27 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } } }); + } else { + // Sometimes ESS don't have the entity they are trying to edit in their local tree. In this case, + // convertPropertiesFromScriptSemantics doesn't get called and local* edits will get dropped. + // This is because, on the script side, "position" is in world frame, but in the network + // protocol and in the internal data-structures, "position" is "relative to parent". + // Compensate here. The local* versions will get ignored during the edit-packet encoding. + if (properties.localPositionChanged()) { + properties.setPosition(properties.getLocalPosition()); + } + if (properties.localRotationChanged()) { + properties.setRotation(properties.getLocalRotation()); + } + if (properties.localVelocityChanged()) { + properties.setVelocity(properties.getLocalVelocity()); + } + if (properties.localAngularVelocityChanged()) { + properties.setAngularVelocity(properties.getLocalAngularVelocity()); + } + if (properties.localDimensionsChanged()) { + properties.setDimensions(properties.getLocalDimensions()); + } } }); if (!entityFound) { @@ -577,21 +563,6 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { return; } - auto dimensions = entity->getScaledDimensions(); - float volume = dimensions.x * dimensions.y * dimensions.z; - auto density = entity->getDensity(); - auto velocity = entity->getWorldVelocity().length(); - float cost = calculateCost(density * volume, velocity, 0); - cost *= costMultiplier; - - if (cost > _currentAvatarEnergy) { - shouldDelete = false; - return; - } else { - //debit the avatar energy and continue - emit debitEnergySource(cost); - } - if (entity->getLocked()) { shouldDelete = false; } else { @@ -788,7 +759,7 @@ QVector EntityScriptingInterface::findEntitiesByType(const QString entity foreach(EntityItemPointer entity, entities) { if (entity->getType() == type) { - result << entity->getEntityItemID(); + result << entity->getEntityItemID().toString(); } } } @@ -1812,23 +1783,6 @@ void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, con } } -float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { - return std::abs(mass * (newVelocity - oldVelocity)); -} - -void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) { - // qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy; - _currentAvatarEnergy = energy; -} - -float EntityScriptingInterface::getCostMultiplier() { - return costMultiplier; -} - -void EntityScriptingInterface::setCostMultiplier(float value) { - costMultiplier = value; -} - // TODO move this someplace that makes more sense... bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index b483225390..74e7ddb677 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -94,8 +94,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra * Interface has displayed and so knows about. * * @namespace Entities - * @property {number} currentAvatarEnergy - Deprecated - * @property {number} costMultiplier - Deprecated * @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus. * If no entity has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to * clear keyboard focus. @@ -104,8 +102,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT - Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy) - Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier) Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity) friend EntityPropertyMetadataRequest; @@ -126,7 +122,6 @@ public: void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setEntitiesScriptEngine(QSharedPointer engine); - float calculateCost(float mass, float oldVelocity, float newVelocity); void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } @@ -203,9 +198,9 @@ public slots: * Add a new entity with specified properties. * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. - * @param {boolean} [clientOnly=false] - If true, the entity is created as an avatar entity, otherwise it - * is created on the server. An avatar entity follows you to each domain you visit, rendering at the same world - * coordinates unless it's parented to your avatar. + * @param {boolean} [clientOnly=false] - If true, or if clientOnly is set true in + * the properties, the entity is created as an avatar entity; otherwise it is created on the server. An avatar entity + * follows you to each domain you visit, rendering at the same world coordinates unless it's parented to your avatar. * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}. * @example Create a box entity in front of your avatar. * var entityID = Entities.addEntity({ @@ -699,7 +694,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. * @returns {Vec3} The world coordinates of the voxelCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. * @example Create a PolyVox cube with the 0,0,0 voxel replaced by a sphere. * // Cube PolyVox with 0,0,0 voxel missing. * var polyVox = Entities.addEntity({ @@ -734,7 +729,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} worldCoords - The world coordinates. May be outside the entity's bounding box. * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be fractional. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); @@ -746,7 +741,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. * @returns {Vec3} The local coordinates of the voxelCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. * @example Get the world dimensions of a voxel in a PolyVox entity. * var polyVox = Entities.addEntity({ * type: "PolyVox", @@ -768,7 +763,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} localCoords - The local coordinates. May be outside the entity's bounding box. * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be fractional. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); @@ -1834,14 +1829,6 @@ signals: */ void clearingEntities(); - /**jsdoc - * @function Entities.debitEnergySource - * @param {number} value - The amount to debit. - * @returns {Signal} - * @deprecated This function is deprecated and will soon be removed. - */ - void debitEnergySource(float value); - /**jsdoc * Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the * script's EventBridge. @@ -1882,14 +1869,8 @@ private: QSharedPointer _entitiesScriptEngine; bool _bidOnSimulationOwnership { false }; - float _currentAvatarEnergy = { FLT_MAX }; - float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } - void setCurrentAvatarEnergy(float energy); ActivityTracking _activityTracking; - float costMultiplier = { 0.01f }; - float getCostMultiplier(); - void setCostMultiplier(float value); }; #endif // hifi_EntityScriptingInterface_h diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 36b0d8ab2d..d034ddedbe 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -20,7 +20,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { if (_entityTree && _entityTree != tree) { _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); @@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { void EntitySimulation::updateEntities() { QMutexLocker lock(&_mutex); - quint64 now = usecTimestampNow(); + uint64_t now = usecTimestampNow(); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); @@ -40,18 +40,14 @@ void EntitySimulation::updateEntities() { sortEntitiesThatMoved(); } -void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { +void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) { QMutexLocker lock(&_mutex); - for (auto entity : _entitiesToDelete) { - // push this entity onto the external list - entitiesToDelete.push_back(entity); - } - _entitiesToDelete.clear(); + entitiesToDelete.swap(_deadEntities); + _deadEntities.clear(); } void EntitySimulation::removeEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - // remove from all internal lists except _entitiesToDelete + // remove from all internal lists except _deadEntities _mortalEntities.remove(entity); _entitiesToUpdate.remove(entity); _entitiesToSort.remove(entity); @@ -67,42 +63,23 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { QMutexLocker lock(&_mutex); entity->clearActions(getThisPointer()); removeEntityInternal(entity); - _entitiesToDelete.insert(entity); - } -} - -void EntitySimulation::addEntityInternal(EntityItemPointer entity) { - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - QMutexLocker lock(&_mutex); - _simpleKinematicEntities.insert(entity); - entity->setLastSimulated(usecTimestampNow()); - } -} - -void EntitySimulation::changeEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - int numKinematicEntities = _simpleKinematicEntities.size(); - _simpleKinematicEntities.insert(entity); - if (numKinematicEntities != _simpleKinematicEntities.size()) { - entity->setLastSimulated(usecTimestampNow()); + if (entity->getElement()) { + _deadEntities.insert(entity); } - } else { - _simpleKinematicEntities.remove(entity); } } // protected -void EntitySimulation::expireMortalEntities(const quint64& now) { +void EntitySimulation::expireMortalEntities(uint64_t now) { if (now > _nextExpiry) { PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _mortalEntities.begin(); while (itemItr != _mortalEntities.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < now) { itemItr = _mortalEntities.erase(itemItr); entity->die(); @@ -122,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { } // protected -void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { +void EntitySimulation::callUpdateOnEntitiesThatNeedIt(uint64_t now) { PerformanceTimer perfTimer("updatingEntities"); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); @@ -176,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { entity->deserializeActions(); if (entity->isMortal()) { _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < _nextExpiry) { _nextExpiry = expiry; } @@ -207,7 +184,6 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. - bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & Simulation::DIRTY_POSITION) { AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); @@ -217,50 +193,45 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; entity->die(); prepareEntityForDelete(entity); - wasRemoved = true; + return; } } - if (!wasRemoved) { - if (dirtyFlags & Simulation::DIRTY_LIFETIME) { - if (entity->isMortal()) { - _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); - if (expiry < _nextExpiry) { - _nextExpiry = expiry; - } - } else { - _mortalEntities.remove(entity); + + if (dirtyFlags & Simulation::DIRTY_LIFETIME) { + if (entity->isMortal()) { + _mortalEntities.insert(entity); + uint64_t expiry = entity->getExpiry(); + if (expiry < _nextExpiry) { + _nextExpiry = expiry; } - entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME); - } - if (entity->needsToCallUpdate()) { - _entitiesToUpdate.insert(entity); } else { - _entitiesToUpdate.remove(entity); + _mortalEntities.remove(entity); } - changeEntityInternal(entity); + entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME); } + if (entity->needsToCallUpdate()) { + _entitiesToUpdate.insert(entity); + } else { + _entitiesToUpdate.remove(entity); + } + changeEntityInternal(entity); } void EntitySimulation::clearEntities() { QMutexLocker lock(&_mutex); _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); clearEntitiesInternal(); - for (auto entity : _allEntities) { - entity->setSimulated(false); - entity->die(); - } _allEntities.clear(); - _entitiesToDelete.clear(); + _deadEntities.clear(); } -void EntitySimulation::moveSimpleKinematics(const quint64& now) { +void EntitySimulation::moveSimpleKinematics(uint64_t now) { PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 1c633aa9dc..b19e1c33d3 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -12,6 +12,8 @@ #ifndef hifi_EntitySimulation_h #define hifi_EntitySimulation_h +#include + #include #include #include @@ -43,9 +45,8 @@ const int DIRTY_SIMULATION_FLAGS = Simulation::DIRTY_SIMULATOR_ID; class EntitySimulation : public QObject, public std::enable_shared_from_this { -Q_OBJECT public: - EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { } + EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits::max()) { } virtual ~EntitySimulation() { setEntityTree(NULL); } inline EntitySimulationPointer getThisPointer() const { @@ -57,8 +58,6 @@ public: void updateEntities(); -// friend class EntityTree; - virtual void addDynamic(EntityDynamicPointer dynamic); virtual void removeDynamic(const QUuid dynamicID); virtual void removeDynamics(QList dynamicIDsToRemove); @@ -74,29 +73,26 @@ public: void clearEntities(); - void moveSimpleKinematics(const quint64& now); + void moveSimpleKinematics(uint64_t now); EntityTreePointer getEntityTree() { return _entityTree; } - virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete); + virtual void takeDeadEntities(SetOfEntities& entitiesToDelete); /// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others. virtual void prepareEntityForDelete(EntityItemPointer entity); -signals: - void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); - protected: // These pure virtual methods are protected because they are not to be called will-nilly. The base class // calls them in the right places. - virtual void updateEntitiesInternal(const quint64& now) = 0; - virtual void addEntityInternal(EntityItemPointer entity); - virtual void removeEntityInternal(EntityItemPointer entity) = 0; - virtual void changeEntityInternal(EntityItemPointer entity); + virtual void updateEntitiesInternal(uint64_t now) = 0; + virtual void addEntityInternal(EntityItemPointer entity) = 0; + virtual void removeEntityInternal(EntityItemPointer entity); + virtual void changeEntityInternal(EntityItemPointer entity) = 0; virtual void clearEntitiesInternal() = 0; - void expireMortalEntities(const quint64& now); - void callUpdateOnEntitiesThatNeedIt(const quint64& now); + void expireMortalEntities(uint64_t now); + void callUpdateOnEntitiesThatNeedIt(uint64_t now); virtual void sortEntitiesThatMoved(); QMutex _mutex{ QMutex::Recursive }; @@ -108,7 +104,7 @@ protected: QMutex _dynamicsMutex { QMutex::Recursive }; protected: - SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) + SetOfEntities _deadEntities; private: void moveSimpleKinematics(); @@ -120,11 +116,10 @@ private: // An entity may be in more than one list. SetOfEntities _allEntities; // tracks all entities added the simulation SetOfEntities _mortalEntities; // entities that have an expiry - quint64 _nextExpiry; + uint64_t _nextExpiry; SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() - }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 75f024d0b9..4d0211a1b7 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); - // this would be a good place to clean up our entities... if (_simulation) { _simulation->clearEntities(); } @@ -113,6 +112,11 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { resetClientEditStats(); clearDeletedEntities(); + + { + QWriteLocker locker(&_needsParentFixupLock); + _needsParentFixup.clear(); + } } void EntityTree::readBitstreamToTree(const unsigned char* bitstream, @@ -260,7 +264,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { return; } } - + // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); @@ -371,12 +375,18 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti simulationBlocked = false; } } + if (!simulationBlocked) { + entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); + } } else { // the entire update is suspect --> ignore it return false; } } else if (simulationBlocked) { simulationBlocked = senderID != entity->getSimulatorID(); + if (!simulationBlocked) { + entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); + } } if (simulationBlocked) { // squash ownership and physics-related changes. @@ -425,8 +435,8 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti if (!childEntity) { continue; } - EntityTreeElementPointer containingElement = childEntity->getElement(); - if (!containingElement) { + EntityTreeElementPointer childContainingElement = childEntity->getElement(); + if (!childContainingElement) { continue; } @@ -440,7 +450,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti addToNeedsParentFixupList(childEntity); } - UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube); + UpdateEntityOperator theChildOperator(getThisPointer(), childContainingElement, childEntity, queryCube); recurseTreeWithOperator(&theChildOperator); foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { if (childChild && childChild->getNestableType() == NestableType::Entity) { @@ -453,12 +463,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { - if (_simulation) { + if (entity->isSimulated()) { + assert((bool)_simulation); if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->changeEntity(entity); } } else { - // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly + // normally the _simulation clears ALL dirtyFlags, but when not possible we do it explicitly entity->clearDirtyFlags(); } } @@ -469,7 +480,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti if (entityScriptBefore != entityScriptAfter || reload) { emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed } - } + } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). if (!entity->getElement()) { @@ -551,8 +562,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) { assert(simulation->getEntityTree().get() == this); } if (_simulation && _simulation != simulation) { - // It's important to clearEntities() on the simulation since taht will update each - // EntityItem::_simulationState correctly so as to not confuse the next _simulation. _simulation->clearEntities(); } _simulation = simulation; @@ -650,7 +659,7 @@ void EntityTree::deleteEntities(QSet entityIDs, bool force, bool i emit deletingEntityPointer(existingEntity.get()); } - if (theOperator.getEntities().size() > 0) { + if (!theOperator.getEntities().empty()) { recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; @@ -692,7 +701,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) trackDeletedEntity(theEntity->getEntityItemID()); } - if (_simulation) { + if (theEntity->isSimulated()) { _simulation->prepareEntityForDelete(theEntity); } } @@ -1151,7 +1160,9 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) }); connect(_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { qCDebug(entities) << "Ownership challenge timed out, deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); if (_challengeOwnershipTimeoutTimer) { _challengeOwnershipTimeoutTimer->stop(); _challengeOwnershipTimeoutTimer->deleteLater(); @@ -1259,7 +1270,9 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri if (text == "") { qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity..."; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else { qCDebug(entities) << "Challenging ownership of Cert ID" << certID; // 2. Send the nonce to the rezzing avatar's node @@ -1322,8 +1335,7 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt request["certificate_id"] = certID; networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); connect(networkReply, &QNetworkReply::finished, [=]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); @@ -1332,14 +1344,20 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt if (networkReply->error() == QNetworkReply::NoError) { if (!jsonObject["invalid_reason"].toString().isEmpty()) { qCDebug(entities) << "invalid_reason not empty, deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { qCDebug(entities) << "'transfer_status' is 'failed', deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { if (isRetryingValidation) { qCDebug(entities) << "'transfer_status' is 'pending' after retry, deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else { if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "startPendingTransferStatusTimer", @@ -1362,7 +1380,9 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID << "More info:" << jsonObject; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } networkReply->deleteLater(); @@ -1630,11 +1650,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); } } else { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("^Edit failed.*"); - qCDebug(entities) << "Edit failed. [" << message.getType() <<"] " << + HIFI_FCDEBUG(entities(), "Edit failed. [" << message.getType() <<"] " << "entity id:" << entityItemID << - "existingEntity pointer:" << existingEntity.get(); + "existingEntity pointer:" << existingEntity.get()); } } @@ -1688,7 +1706,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod } void EntityTree::entityChanged(EntityItemPointer entity) { - if (_simulation) { + if (entity->isSimulated()) { _simulation->changeEntity(entity); } } @@ -1796,19 +1814,19 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { void EntityTree::update(bool simulate) { PROFILE_RANGE(simulation_physics, "UpdateTree"); - fixupNeedsParentFixups(); - if (simulate && _simulation) { - withWriteLock([&] { + withWriteLock([&] { + fixupNeedsParentFixups(); + if (simulate && _simulation) { _simulation->updateEntities(); { PROFILE_RANGE(simulation_physics, "Deletes"); - VectorOfEntities pendingDeletes; - _simulation->takeEntitiesToDelete(pendingDeletes); - if (pendingDeletes.size() > 0) { + SetOfEntities deadEntities; + _simulation->takeDeadEntities(deadEntities); + if (!deadEntities.empty()) { // translate into list of ID's QSet idsToDelete; - for (auto entity : pendingDeletes) { + for (auto entity : deadEntities) { idsToDelete.insert(entity->getEntityItemID()); } @@ -1816,8 +1834,8 @@ void EntityTree::update(bool simulate) { deleteEntities(idsToDelete, true); } } - }); - } + } + }); } quint64 EntityTree::getAdjustedConsiderSince(quint64 sinceTime) { @@ -2286,7 +2304,9 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer QScriptEngine scriptEngine; RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, _myAvatar); - recurseTreeWithOperator(&theOperator); + withReadLock([&] { + recurseTreeWithOperator(&theOperator); + }); return true; } @@ -2303,6 +2323,16 @@ bool EntityTree::readFromMap(QVariantMap& map) { _persistDataVersion = map["DataVersion"].toInt(); } + _namedPaths.clear(); + if (map.contains("Paths")) { + QVariantMap namedPathsMap = map["Paths"].toMap(); + for(QVariantMap::const_iterator iter = namedPathsMap.begin(); iter != namedPathsMap.end(); ++iter) { + QString namedPathName = iter.key(); + QString namedPathViewPoint = iter.value().toString(); + _namedPaths[namedPathName] = namedPathViewPoint; + } + } + // map will have a top-level list keyed as "Entities". This will be extracted // and iterated over. Each member of this list is converted to a QVariantMap, then // to a QScriptValue, and then to EntityItemProperties. These properties are used @@ -2378,6 +2408,30 @@ bool EntityTree::readFromMap(QVariantMap& map) { } } + // Convert old materials so that they use materialData instead of userData + if (contentVersion < (int)EntityVersion::MaterialData && properties.getType() == EntityTypes::EntityType::Material) { + if (properties.getMaterialURL().startsWith("userData")) { + QString materialURL = properties.getMaterialURL(); + properties.setMaterialURL(materialURL.replace("userData", "materialData")); + + QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); + QJsonObject materialData; + QJsonValue materialVersion = userData["materialVersion"]; + if (!materialVersion.isNull()) { + materialData.insert("materialVersion", materialVersion); + userData.remove("materialVersion"); + } + QJsonValue materials = userData["materials"]; + if (!materials.isNull()) { + materialData.insert("materials", materials); + userData.remove("materials"); + } + + properties.setMaterialData(QJsonDocument(materialData).toJson()); + properties.setUserData(QJsonDocument(userData).toJson()); + } + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5f69714432..a080801a0e 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -88,7 +88,6 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing - virtual bool getWantSVOfileVersions() const override { return true; } virtual PacketType expectedDataPacketType() const override { return PacketType::EntityData; } virtual bool handlesEditPacketType(PacketType packetType) const override; void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); @@ -107,11 +106,7 @@ public: virtual bool rootElementHasData() const override { return true; } - // the root at least needs to store the number of entities in the packet/buffer - virtual int minimumRequiredRootDataBytes() const override { return sizeof(uint16_t); } - virtual bool suppressEmptySubtrees() const override { return false; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const override; - virtual bool mustIncludeAllChildData() const override { return false; } virtual void update() override { update(true); } @@ -301,6 +296,8 @@ public: static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName); static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName); + std::map getNamedPaths() const { return _namedPaths; } + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -417,6 +414,8 @@ private: static std::function _removeMaterialFromOverlayOperator; bool _serverlessDomain { false }; + + std::map _namedPaths; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 1ae55bc333..cbcddfc57b 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -67,455 +67,6 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons } } -void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) { - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - // Check to see if this element yet has encode data... if it doesn't create it - if (!extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData { new EntityTreeElementExtraEncodeData() }; - entityTreeElementExtraEncodeData->elementCompleted = (_entityItems.size() == 0); - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer child = getChildAtIndex(i); - if (!child) { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed - } else { - if (child->hasEntities()) { - entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING - } else { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed - } - } - } - forEachEntity([&](EntityItemPointer entity) { - entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); - }); - - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } -} - -bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex]; - - // If we haven't completely sent the child yet, then we should include it - return !childCompleted; - } - - // I'm not sure this should ever happen, since we should have the extra encode data if we're considering - // the child data for this element - assert(false); - return false; -} - -bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { - EntityTreeElementPointer childElement = getChildAtIndex(childIndex); - if (childElement->alreadyFullyEncoded(params)) { - return false; - } - - return true; // if we don't know otherwise than recurse! -} - -bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const { - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - // If we know that ALL subtrees below us have already been recursed, then we don't - // need to recurse this child. - return entityTreeElementExtraEncodeData->subtreeCompleted; - } - return false; -} - -void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - if (childAppendState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->childCompleted[childIndex] = true; - } - } else { - assert(false); // this shouldn't happen! - } -} - - - - -void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const { - const bool wantDebug = false; - - if (wantDebug) { - qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube; - } - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - assert(extraEncodeData->contains(this)); - - EntityTreeElementExtraEncodeDataPointer thisExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - // Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion() - // which means, it's possible that our parent element hasn't finished encoding OUR data... so - // in this case, our children may be complete, and we should clean up their encode data... - // but not necessarily cleanup our own encode data... - // - // If we're really complete here's what must be true... - // 1) our own data must be complete - // 2) the data for all our immediate children must be complete. - // However, the following might also be the case... - // 1) it's ok for our child trees to not yet be fully encoded/complete... - // SO LONG AS... the our child's node is in the bag ready for encoding - - bool someChildTreeNotComplete = false; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer childElement = getChildAtIndex(i); - if (childElement) { - - // why would this ever fail??? - // If we've encoding this element before... but we're coming back a second time in an attempt to - // encode our parent... this might happen. - if (extraEncodeData->contains(childElement.get())) { - EntityTreeElementExtraEncodeDataPointer childExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[childElement.get()]); - - if (wantDebug) { - qCDebug(entities) << "checking child: " << childElement->_cube; - qCDebug(entities) << " childElement->isLeaf():" << childElement->isLeaf(); - qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted; - qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted; - } - - if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) { - if (wantDebug) { - qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!"; - } - childExtraEncodeData->subtreeCompleted = true; - } - - if (!childExtraEncodeData->elementCompleted || !childExtraEncodeData->subtreeCompleted) { - someChildTreeNotComplete = true; - } - } - } - } - - if (wantDebug) { - qCDebug(entities) << "for this element: " << _cube; - qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted; - qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - } - - thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete; - - if (wantDebug) { - qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted; - qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - - if (thisExtraEncodeData->subtreeCompleted) { - qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - } - } -} - -OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, - EncodeBitstreamParams& params) const { - - OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... - - auto entityNodeData = static_cast(params.nodeData); - Q_ASSERT_X(entityNodeData, "EntityTreeElement::appendElementData", "expected params.nodeData not to be null"); - - // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = NULL; - bool hadElementExtraData = false; - if (extraEncodeData && extraEncodeData->contains(this)) { - entityTreeElementExtraEncodeData = - std::static_pointer_cast((*extraEncodeData)[this]); - hadElementExtraData = true; - } else { - // if there wasn't one already, then create one - entityTreeElementExtraEncodeData.reset(new EntityTreeElementExtraEncodeData()); - entityTreeElementExtraEncodeData->elementCompleted = !hasContent(); - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer child = getChildAtIndex(i); - if (!child) { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed - } else { - if (child->hasEntities()) { - entityTreeElementExtraEncodeData->childCompleted[i] = false; - } else { - // if the child doesn't have enities, it is completed - entityTreeElementExtraEncodeData->childCompleted[i] = true; - } - } - } - forEachEntity([&](EntityItemPointer entity) { - entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); - }); - } - - //assert(extraEncodeData); - //assert(extraEncodeData->contains(this)); - //entityTreeElementExtraEncodeData = std::static_pointer_cast((*extraEncodeData)[this]); - - LevelDetails elementLevel = packetData->startLevel(); - - // write our entities out... first determine which of the entities are in view based on our params - uint16_t numberOfEntities = 0; - uint16_t actualNumberOfEntities = 0; - int numberOfEntitiesOffset = 0; - withReadLock([&] { - QVector indexesOfEntitiesToInclude; - - // It's possible that our element has been previous completed. In this case we'll simply not include any of our - // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we - // need to handle the case where our sibling elements need encoding but we don't. - if (!entityTreeElementExtraEncodeData->elementCompleted) { - - - // we have an EntityNodeData instance - // so we should assume that means we might have JSON filters to check - auto jsonFilters = entityNodeData->getJSONParameters(); - - - for (uint16_t i = 0; i < _entityItems.size(); i++) { - EntityItemPointer entity = _entityItems[i]; - bool includeThisEntity = true; - - if (!params.forceSendScene && entity->getLastChangedOnServer() < entityNodeData->getLastTimeBagEmpty()) { - includeThisEntity = false; - } - - // if this entity has been updated since our last full send and there are json filters, check them - if (includeThisEntity && !jsonFilters.isEmpty()) { - - // if params include JSON filters, check if this entity matches - bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); - - if (entityMatchesFilters) { - // make sure this entity is in the set of entities sent last frame - entityNodeData->insertSentFilteredEntity(entity->getID()); - } else if (entityNodeData->sentFilteredEntity(entity->getID())) { - // this entity matched in the previous frame - we send it still so the client realizes it just - // fell outside of their filter - entityNodeData->removeSentFilteredEntity(entity->getID()); - } else if (!entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { - // we don't send this entity because - // (1) it didn't match our filter - // (2) it didn't match our filter last frame - // (3) it isn't one the JSON query flags told us we should still include - includeThisEntity = false; - } - } - - if (includeThisEntity && hadElementExtraData) { - includeThisEntity = entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); - } - - // we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum - // which can sometimes not be the case when JSON filters are sent - if (entityNodeData->getUsesFrustum() && (includeThisEntity || params.recurseEverything)) { - - // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of - // simulation changing what's visible. consider the case where the entity contains an angular velocity - // the entity may not be in view and then in view a frame later, let the client side handle it's view - // frustum culling on rendering. - bool success; - AACube entityCube = entity->getQueryAACube(success); - if (!success || !params.viewFrustum.cubeIntersectsKeyhole(entityCube)) { - includeThisEntity = false; // out of view, don't include it - } else { - // Check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - success = true; - // we can't cull a parent-entity by its dimensions because the child may be larger. we need to - // avoid sending details about a child but not the parent. the parent's queryAACube should have - // been adjusted to encompass the queryAACube of the child. - AABox entityBounds = entity->hasChildren() ? AABox(entityCube) : entity->getAABox(success); - if (!success) { - // if this entity is a child of an avatar, the entity-server wont be able to determine its - // AABox. If this happens, fall back to the queryAACube. - entityBounds = AABox(entityCube); - } - auto renderAccuracy = calculateRenderAccuracy(params.viewFrustum.getPosition(), - entityBounds, - params.octreeElementSizeScale, - params.boundaryLevelAdjust); - if (renderAccuracy <= 0.0f) { - includeThisEntity = false; // too small, don't include it - - #ifdef WANT_LOD_DEBUGGING - qCDebug(entities) << "skipping entity - TOO SMALL - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "..bounds:" << entityBounds << "\n" - << "....cell:" << getAACube(); - #endif - } - } - } - - if (includeThisEntity) { - #ifdef WANT_LOD_DEBUGGING - qCDebug(entities) << "including entity - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "....cell:" << getAACube(); - #endif - indexesOfEntitiesToInclude << i; - numberOfEntities++; - } else { - // if the extra data included this entity, and we've decided to not include the entity, then - // we can treat it as if it was completed. - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); - } - } - } - - numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); - bool successAppendEntityCount = packetData->appendValue(numberOfEntities); - - if (successAppendEntityCount) { - foreach(uint16_t i, indexesOfEntitiesToInclude) { - EntityItemPointer entity = _entityItems[i]; - LevelDetails entityLevel = packetData->startLevel(); - OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData, - params, entityTreeElementExtraEncodeData); - - // If none of this entity data was able to be appended, then discard it - // and don't include it in our entity count - if (appendEntityState == OctreeElement::NONE) { - packetData->discardLevel(entityLevel); - } else { - // If either ALL or some of it got appended, then end the level (commit it) - // and include the entity in our final count of entities - packetData->endLevel(entityLevel); - actualNumberOfEntities++; - - // If the entity item got completely appended, then we can remove it from the extra encode data - if (appendEntityState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); - } - } - - // If any part of the entity items didn't fit, then the element is considered partial - // NOTE: if the entity item didn't fit or only partially fit, then the entity item should have - // added itself to the extra encode data. - if (appendEntityState != OctreeElement::COMPLETED) { - appendElementState = OctreeElement::PARTIAL; - } - } - } else { - // we we couldn't add the entity count, then we couldn't add anything for this element and we're in a NONE state - appendElementState = OctreeElement::NONE; - } - }); - - // If we were provided with extraEncodeData, and we allocated and/or got entityTreeElementExtraEncodeData - // then we need to do some additional processing, namely make sure our extraEncodeData is up to date for - // this octree element. - if (extraEncodeData && entityTreeElementExtraEncodeData) { - - // After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data. - // Only our parent can remove our extra data in these cases and only after it knows that all of its - // children have been encoded. - // - // FIXME -- this comment seems wrong.... - // - // If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data - // since that will signal that the entire element needs to be encoded on the next attempt - if (appendElementState == OctreeElement::NONE) { - - if (!entityTreeElementExtraEncodeData->elementCompleted && entityTreeElementExtraEncodeData->entities.size() == 0) { - // TODO: we used to delete the extra encode data here. But changing the logic around - // this is now a dead code branch. Clean this up! - } else { - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } - } else { - - // If we weren't previously completed, check to see if we are - if (!entityTreeElementExtraEncodeData->elementCompleted) { - // If all of our items have been encoded, then we are complete as an element. - if (entityTreeElementExtraEncodeData->entities.size() == 0) { - entityTreeElementExtraEncodeData->elementCompleted = true; - } - } - - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } - } - - // Determine if no entities at all were able to fit - bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0); - - // If we wrote fewer entities than we expected, update the number of entities in our packet - bool successUpdateEntityCount = true; - if (numberOfEntities != actualNumberOfEntities) { - successUpdateEntityCount = packetData->updatePriorBytes(numberOfEntitiesOffset, - (const unsigned char*)&actualNumberOfEntities, sizeof(actualNumberOfEntities)); - } - - // If we weren't able to update our entity count, or we couldn't fit any entities, then - // we should discard our element and return a result of NONE - if (!successUpdateEntityCount) { - packetData->discardLevel(elementLevel); - appendElementState = OctreeElement::NONE; - } else { - if (noEntitiesFit) { - //appendElementState = OctreeElement::PARTIAL; - packetData->discardLevel(elementLevel); - appendElementState = OctreeElement::NONE; - } else { - packetData->endLevel(elementLevel); - } - } - return appendElementState; -} - bool EntityTreeElement::containsEntityBounds(EntityItemPointer entity) const { bool success; auto queryCube = entity->getQueryAACube(success); diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index b219d64d9d..76e1e40812 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -121,17 +121,6 @@ public: virtual bool requiresSplit() const override { return false; } virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const override; - virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) override; - virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const override; - virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const override; - virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const override; - virtual void elementEncodeComplete(EncodeBitstreamParams& params) const override; - - bool alreadyFullyEncoded(EncodeBitstreamParams& params) const; - - /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData, - EncodeBitstreamParams& params) const override; /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. @@ -243,14 +232,10 @@ public: return std::static_pointer_cast(shared_from_this()); } - void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); } - uint64_t getLastChangedContent() const { return _lastChangedContent; } - protected: virtual void init(unsigned char * octalCode) override; EntityTreePointer _myTree; EntityItems _entityItems; - uint64_t _lastChangedContent { 0 }; }; #endif // hifi_EntityTreeElement_h diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 3f7fc5f799..f0fbb20f98 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -186,7 +186,6 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_IS_SPOTLIGHT; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 00196d7b23..92a1c25970 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -128,7 +128,6 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 375453e0e9..84f9acf5f5 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -26,7 +26,6 @@ class LineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 137d6ef396..489ba5772c 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -27,6 +27,10 @@ MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : Entit _type = EntityTypes::Material; } +MaterialEntityItem::~MaterialEntityItem() { + removeMaterial(); +} + EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); @@ -36,6 +40,7 @@ EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desir COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingPos, getMaterialMappingPos); COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingScale, getMaterialMappingScale); COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingRot, getMaterialMappingRot); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialData, getMaterialData); return properties; } @@ -49,6 +54,7 @@ bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingPos, setMaterialMappingPos); SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingScale, setMaterialMappingScale); SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingRot, setMaterialMappingRot); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialData, setMaterialData); if (somethingChanged) { bool wantDebug = false; @@ -78,12 +84,11 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, glm::vec2, setMaterialMappingPos); READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, glm::vec2, setMaterialMappingScale); READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); + READ_ENTITY_PROPERTY(PROP_MATERIAL_DATA, QString, setMaterialData); return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_MATERIAL_URL; @@ -93,6 +98,7 @@ EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParam requestedProperties += PROP_MATERIAL_MAPPING_POS; requestedProperties += PROP_MATERIAL_MAPPING_SCALE; requestedProperties += PROP_MATERIAL_MAPPING_ROT; + requestedProperties += PROP_MATERIAL_DATA; return requestedProperties; } @@ -112,6 +118,7 @@ void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, Encode APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, getMaterialMappingPos()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, getMaterialMappingScale()); APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, getMaterialMappingRot()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, getMaterialData()); } void MaterialEntityItem::debugDump() const { @@ -145,9 +152,9 @@ std::shared_ptr MaterialEntityItem::getMaterial() const { } } -void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool userDataChanged) { - bool usingUserData = materialURLString.startsWith("userData"); - if (_materialURL != materialURLString || (usingUserData && userDataChanged)) { +void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool materialDataChanged) { + bool usingMaterialData = materialDataChanged || materialURLString.startsWith("materialData"); + if (_materialURL != materialURLString || (usingMaterialData && materialDataChanged)) { removeMaterial(); _materialURL = materialURLString; @@ -156,8 +163,8 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u _currentMaterialName = split.last().toStdString(); } - if (usingUserData) { - _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()), materialURLString); + if (usingMaterialData) { + _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getMaterialData().toUtf8()), materialURLString); // Since our material changed, the current name might not be valid anymore, so we need to update setCurrentMaterialName(_currentMaterialName); @@ -191,11 +198,11 @@ void MaterialEntityItem::setCurrentMaterialName(const std::string& currentMateri } } -void MaterialEntityItem::setUserData(const QString& userData) { - if (_userData != userData) { - EntityItem::setUserData(userData); - if (_materialURL.startsWith("userData")) { - // Trigger material update when user data changes +void MaterialEntityItem::setMaterialData(const QString& materialData) { + if (_materialData != materialData) { + _materialData = materialData; + if (_materialURL.startsWith("materialData")) { + // Trigger material update when material data changes setMaterialURL(_materialURL, true); } } @@ -249,26 +256,13 @@ void MaterialEntityItem::setParentID(const QUuid& parentID) { } } -void MaterialEntityItem::setClientOnly(bool clientOnly) { - if (getClientOnly() != clientOnly) { - removeMaterial(); - EntityItem::setClientOnly(clientOnly); - applyMaterial(); - } -} - -void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) { - if (getOwningAvatarID() != owningAvatarID) { - removeMaterial(); - EntityItem::setOwningAvatarID(owningAvatarID); - applyMaterial(); - } -} - void MaterialEntityItem::removeMaterial() { graphics::MaterialPointer material = getMaterial(); - QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); - if (!material || parentID.isNull()) { + if (!material) { + return; + } + QUuid parentID = getParentID(); + if (parentID.isNull()) { return; } @@ -291,7 +285,7 @@ void MaterialEntityItem::removeMaterial() { void MaterialEntityItem::applyMaterial() { _retryApply = false; graphics::MaterialPointer material = getMaterial(); - QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); + QUuid parentID = getParentID(); if (!material || parentID.isNull()) { return; } @@ -325,15 +319,10 @@ void MaterialEntityItem::postParentFixup() { applyMaterial(); } -void MaterialEntityItem::preDelete() { - EntityItem::preDelete(); - removeMaterial(); -} - void MaterialEntityItem::update(const quint64& now) { if (_retryApply) { applyMaterial(); } EntityItem::update(now); -} \ No newline at end of file +} diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index f77077a782..30743850dd 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -21,6 +21,7 @@ public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); MaterialEntityItem(const EntityItemID& entityItemID); + ~MaterialEntityItem(); ALLOW_INSTANTIATION // This class can be instantiated @@ -31,7 +32,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, @@ -53,7 +53,7 @@ public: virtual void setUnscaledDimensions(const glm::vec3& value) override; QString getMaterialURL() const { return _materialURL; } - void setMaterialURL(const QString& materialURLString, bool userDataChanged = false); + void setMaterialURL(const QString& materialURLString, bool materialDataChanged = false); void setCurrentMaterialName(const std::string& currentMaterialName); @@ -73,21 +73,20 @@ public: float getMaterialMappingRot() const { return _materialMappingRot; } void setMaterialMappingRot(const float& materialMappingRot); + QString getMaterialData() const { return _materialData; } + void setMaterialData(const QString& materialData); + std::shared_ptr getMaterial() const; - void setUserData(const QString& userData) override; void setParentID(const QUuid& parentID) override; - void setClientOnly(bool clientOnly) override; - void setOwningAvatarID(const QUuid& owningAvatarID) override; void applyMaterial(); void removeMaterial(); void postParentFixup() override; - void preDelete() override; private: - // URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material. + // URL for this material. Currently, only JSON format is supported. Set to "materialData" to use the material data to live edit a material. // The following fields are supported in the JSON: // materialVersion: a uint for the version of this network material (currently, only 1 is supported) // materials, which is either an object or an array of objects, each with the following properties: @@ -117,6 +116,7 @@ private: glm::vec2 _materialMappingScale { 1, 1 }; // How much to rotate this material within its parent's UV-space (degrees) float _materialMappingRot { 0 }; + QString _materialData; NetworkMaterialResourcePointer _networkMaterial; NetworkMaterialResource::ParsedMaterials _parsedMaterials; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index bec7bc1906..be62664ff9 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -144,7 +144,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); @@ -721,4 +720,4 @@ bool ModelEntityItem::isAnimatingSomething() const { _animationProperties.getRunning() && (_animationProperties.getFPS() != 0.0f); }); -} \ No newline at end of file +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index c2109ba51f..327606ae2f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -30,7 +30,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 7d27011c56..d9ef5e2178 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -503,8 +503,6 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 498d13058e..420c570e8d 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -216,8 +216,6 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 2dc8befe97..871c451c50 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -26,7 +26,6 @@ class PolyLineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 84ce83d3a1..ed3372818a 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -183,8 +183,6 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_VOXEL_VOLUME_SIZE; diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 0ddfe3e8cc..4dfe7b9535 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -26,7 +26,6 @@ class PolyVoxEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 60ba429938..520d892682 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -117,8 +117,10 @@ ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - properties.setColor(getXColor()); properties.setShape(entity::stringFromShape(getShape())); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + return properties; } @@ -186,8 +188,6 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SHAPE; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 97202afcf7..adc33b764b 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -105,7 +105,7 @@ public: protected: - float _alpha { 1 }; // FIXME: This property is not used. + float _alpha { 1.0f }; rgbColor _color; entity::Shape _shape { entity::Shape::Sphere }; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 3203c2968c..301011928b 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -//#include - #include "SimpleEntitySimulation.h" #include @@ -18,7 +16,7 @@ #include "EntityItem.h" #include "EntitiesLogging.h" -const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; +const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { QMutexLocker lock(&_mutex); @@ -27,100 +25,85 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { EntityItemPointer entity = *itemItr; if (entity->getSimulatorID() == ownerID) { // the simulator has abandonded this object --> remove from owned list - qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID(); itemItr = _entitiesWithSimulationOwner.erase(itemItr); if (entity->getDynamic() && entity->hasLocalVelocity()) { // it is still moving dynamically --> add to orphaned list _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); } // remove ownership and dirty all the tree elements that contain the it entity->clearSimulationOwnership(); entity->markAsChangedOnServer(); - DirtyOctreeElementOperator op(entity->getElement()); - getEntityTree()->recurseTreeWithOperator(&op); + if (auto element = entity->getElement()) { + auto tree = getEntityTree(); + tree->withReadLock([&] { + DirtyOctreeElementOperator op(element); + tree->recurseTreeWithOperator(&op); + }); + } } else { ++itemItr; } } } -void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { - if (now > _nextOwnerlessExpiry) { - // search for ownerless objects that have expired - QMutexLocker lock(&_mutex); - _nextOwnerlessExpiry = -1; - SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); - while (itemItr != _entitiesThatNeedSimulationOwner.end()) { - EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < now) { - // no simulators have volunteered ownership --> remove from list - itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); - - if (entity->getSimulatorID().isNull() && entity->getDynamic() && entity->hasLocalVelocity()) { - // zero the derivatives - entity->setVelocity(Vectors::ZERO); - entity->setAngularVelocity(Vectors::ZERO); - entity->setAcceleration(Vectors::ZERO); - - // dirty all the tree elements that contain it - entity->markAsChangedOnServer(); - DirtyOctreeElementOperator op(entity->getElement()); - getEntityTree()->recurseTreeWithOperator(&op); - } - } else { - ++itemItr; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } - } - } - } +void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) { + expireStaleOwnerships(now); + stopOwnerlessEntities(now); } void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { - EntitySimulation::addEntityInternal(entity); + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + QMutexLocker lock(&_mutex); + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } if (!entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); } else if (entity->getDynamic() && entity->hasLocalVelocity()) { QMutexLocker lock(&_mutex); _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); } } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.remove(entity); _entitiesThatNeedSimulationOwner.remove(entity); } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { - EntitySimulation::changeEntityInternal(entity); + { + QMutexLocker lock(&_mutex); + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + int numKinematicEntities = _simpleKinematicEntities.size(); + _simpleKinematicEntities.insert(entity); + if (numKinematicEntities != _simpleKinematicEntities.size()) { + entity->setLastSimulated(usecTimestampNow()); + } + } else { + _simpleKinematicEntities.remove(entity); + } + } if (entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.remove(entity); if (entity->getDynamic() && entity->hasLocalVelocity()) { _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - if (expiry < _nextOwnerlessExpiry) { - _nextOwnerlessExpiry = expiry; - } + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); } } else { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); _entitiesThatNeedSimulationOwner.remove(entity); } entity->clearDirtyFlags(); @@ -141,3 +124,58 @@ void SimpleEntitySimulation::sortEntitiesThatMoved() { } EntitySimulation::sortEntitiesThatMoved(); } + +void SimpleEntitySimulation::expireStaleOwnerships(uint64_t now) { + if (now > _nextStaleOwnershipExpiry) { + _nextStaleOwnershipExpiry = (uint64_t)(-1); + SetOfEntities::iterator itemItr = _entitiesWithSimulationOwner.begin(); + while (itemItr != _entitiesWithSimulationOwner.end()) { + EntityItemPointer entity = *itemItr; + uint64_t expiry = entity->getSimulationOwnershipExpiry(); + if (now > expiry) { + itemItr = _entitiesWithSimulationOwner.erase(itemItr); + + // remove ownership and dirty all the tree elements that contain the it + entity->clearSimulationOwnership(); + entity->markAsChangedOnServer(); + DirtyOctreeElementOperator op(entity->getElement()); + getEntityTree()->recurseTreeWithOperator(&op); + } else { + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, expiry); + ++itemItr; + } + } + } +} + +void SimpleEntitySimulation::stopOwnerlessEntities(uint64_t now) { + if (now > _nextOwnerlessExpiry) { + // search for ownerless objects that have expired + QMutexLocker lock(&_mutex); + _nextOwnerlessExpiry = (uint64_t)(-1); + SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); + while (itemItr != _entitiesThatNeedSimulationOwner.end()) { + EntityItemPointer entity = *itemItr; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + if (expiry < now) { + // no simulators have volunteered ownership --> remove from list + itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); + + if (entity->getSimulatorID().isNull() && entity->getDynamic() && entity->hasLocalVelocity()) { + // zero the derivatives + entity->setVelocity(Vectors::ZERO); + entity->setAngularVelocity(Vectors::ZERO); + entity->setAcceleration(Vectors::ZERO); + + // dirty all the tree elements that contain it + entity->markAsChangedOnServer(); + DirtyOctreeElementOperator op(entity->getElement()); + getEntityTree()->recurseTreeWithOperator(&op); + } + } else { + _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); + ++itemItr; + } + } + } +} diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 9c7c9a374e..8ac9b69e93 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -23,22 +23,26 @@ using SimpleEntitySimulationPointer = std::shared_ptr; class SimpleEntitySimulation : public EntitySimulation { public: SimpleEntitySimulation() : EntitySimulation() { } - virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); } + ~SimpleEntitySimulation() { clearEntitiesInternal(); } void clearOwnership(const QUuid& ownerID); protected: - virtual void updateEntitiesInternal(const quint64& now) override; - virtual void addEntityInternal(EntityItemPointer entity) override; - virtual void removeEntityInternal(EntityItemPointer entity) override; - virtual void changeEntityInternal(EntityItemPointer entity) override; - virtual void clearEntitiesInternal() override; + void updateEntitiesInternal(uint64_t now) override; + void addEntityInternal(EntityItemPointer entity) override; + void removeEntityInternal(EntityItemPointer entity) override; + void changeEntityInternal(EntityItemPointer entity) override; + void clearEntitiesInternal() override; - virtual void sortEntitiesThatMoved() override; + void sortEntitiesThatMoved() override; + + void expireStaleOwnerships(uint64_t now); + void stopOwnerlessEntities(uint64_t now); SetOfEntities _entitiesWithSimulationOwner; SetOfEntities _entitiesThatNeedSimulationOwner; - quint64 _nextOwnerlessExpiry { 0 }; + uint64_t _nextOwnerlessExpiry { 0 }; + uint64_t _nextStaleOwnershipExpiry { (uint64_t)(-1) }; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index 4398582673..b312c6deb9 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -16,9 +16,9 @@ #include -const quint8 PENDING_STATE_NOTHING = 0; -const quint8 PENDING_STATE_TAKE = 1; -const quint8 PENDING_STATE_RELEASE = 2; +const uint8_t PENDING_STATE_NOTHING = 0; +const uint8_t PENDING_STATE_TAKE = 1; +const uint8_t PENDING_STATE_RELEASE = 2; // static const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; @@ -33,7 +33,7 @@ SimulationOwner::SimulationOwner() : { } -SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : +SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) : _id(id), _expiry(0), _pendingBidTimestamp(0), @@ -67,11 +67,11 @@ void SimulationOwner::clear() { _pendingState = PENDING_STATE_NOTHING; } -void SimulationOwner::setPriority(quint8 priority) { +void SimulationOwner::setPriority(uint8_t priority) { _priority = priority; } -void SimulationOwner::promotePriority(quint8 priority) { +void SimulationOwner::promotePriority(uint8_t priority) { if (priority > _priority) { _priority = priority; } @@ -89,7 +89,7 @@ bool SimulationOwner::setID(const QUuid& id) { return false; } -bool SimulationOwner::set(const QUuid& id, quint8 priority) { +bool SimulationOwner::set(const QUuid& id, uint8_t priority) { uint8_t oldPriority = _priority; setPriority(priority); return setID(id) || oldPriority != _priority; @@ -101,22 +101,22 @@ bool SimulationOwner::set(const SimulationOwner& owner) { return setID(owner._id) || oldPriority != _priority; } -void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { +void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) { _pendingBidPriority = priority; _pendingBidTimestamp = timestamp; _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; } void SimulationOwner::updateExpiry() { - const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5; + const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC; _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; } -bool SimulationOwner::pendingRelease(const quint64& timestamp) { +bool SimulationOwner::pendingRelease(uint64_t timestamp) { return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; } -bool SimulationOwner::pendingTake(const quint64& timestamp) { +bool SimulationOwner::pendingTake(uint64_t timestamp) { return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; } @@ -142,7 +142,7 @@ void SimulationOwner::test() { { // test set constructor QUuid id = QUuid::createUuid(); - quint8 priority = 128; + uint8_t priority = 128; SimulationOwner simOwner(id, priority); if (simOwner.isNull()) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; @@ -164,7 +164,7 @@ void SimulationOwner::test() { { // test set() QUuid id = QUuid::createUuid(); - quint8 priority = 1; + uint8_t priority = 1; SimulationOwner simOwner; simOwner.set(id, priority); if (simOwner.isNull()) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 94ed1d9a08..cc2069dcc8 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -18,20 +18,88 @@ #include #include -// Simulation observers will bid to simulate unowned active objects at the lowest possible priority -// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it -// to RECRUIT priority so that other volunteers don't accidentally take over. -const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; -const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; +// HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions +// of the same world. When portions overlap only one participant is allowed to be the authority for any +// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and +// their duty is to send transform/velocity updates for the entity to the central entity-server. +// The entity-server relays updates to other participants who apply them as "state synchronization" +// to their own simulation. +// +// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update: +// { +// "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, +// transform/velocity properties +// } +// +// The entity-server is the authority as to who owns what and may reject a bid. +// The rules for handling a bid are as follows: +// +// (1) A bid may be refused for special ownership restrictions, but otherwise... +// +// (2) A bid at higher priority is accepted +// +// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec) +// of the last ownership transition, otherwise it is accepted +// +// (4) The current owner is the only participant allowed to clear ownership (entity-server can override). +// +// (5) The current owner is the only participant allowed to adjust priority (entity-server can override). +// +// (6) If an owner does not update the transform or velocities of an owned entity within some period +// (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle +// the case when an owner drops off the network. +// +// The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules +// for bidding are as follows: +// +// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the +// simulation owner and priority as if they really did change but doesn't actually modify them +// locally. Thus, if the bid packet is lost the participant will re-send after some period. +// The participant only updates its knowledge of who owns what when it recieves an update from the +// entity-server. An exception is when the participant creates a moving entity: it assumes it starts +// off owning any moving entities it creates. +// +// (8) When an unowned entity becomes active in the physics simulation the participant will +// start a timer and if the entity is still unowned after some period (0.5 seconds) +// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER +// priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to +// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership +// when multiple participants (with variable ping-times to the server) bid simultaneously for a +// recently activated entity. +// +// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127) +// +// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE. +// +// (11) When a participant grabs an entity it will bid at priority = GRAB (=128). +// +// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will +// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger). +// +// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will +// send an update to: clear their ownerhsip, set priority to zero, and set the object's +// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership +// has been cleared until it hears from the entity-server. This, if the packet is lost the +// owner will re-send after some period. +// +// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it +// immediately at priority = VOLUNTEER. +// +// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority +// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it. +// +const uint8_t YIELD_SIMULATION_PRIORITY = 1; +const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1; +const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. -const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80; -const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; -const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; +const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128; +const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower -const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; +const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; class SimulationOwner { @@ -39,25 +107,25 @@ public: static const int NUM_BYTES_ENCODED; SimulationOwner(); - SimulationOwner(const QUuid& id, quint8 priority); + SimulationOwner(const QUuid& id, uint8_t priority); const QUuid& getID() const { return _id; } - const quint64& getExpiry() const { return _expiry; } - quint8 getPriority() const { return _priority; } + const uint64_t& getExpiry() const { return _expiry; } + uint8_t getPriority() const { return _priority; } QByteArray toByteArray() const; bool fromByteArray(const QByteArray& data); void clear(); - void setPriority(quint8 priority); - void promotePriority(quint8 priority); + void setPriority(uint8_t priority); + void promotePriority(uint8_t priority); // return true if id is changed bool setID(const QUuid& id); - bool set(const QUuid& id, quint8 priority); + bool set(const QUuid& id, uint8_t priority); bool set(const SimulationOwner& owner); - void setPendingPriority(quint8 priority, const quint64& timestamp); + void setPendingPriority(uint8_t priority, uint64_t timestamp); bool isNull() const { return _id.isNull(); } bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } @@ -67,11 +135,11 @@ public: bool hasExpired() const { return usecTimestampNow() > _expiry; } uint8_t getPendingPriority() const { return _pendingBidPriority; } - bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE - bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE + bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE + bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE void clearCurrentOwner(); - bool operator>=(quint8 priority) const { return _priority >= priority; } + bool operator>=(uint8_t priority) const { return _priority >= priority; } bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); } bool operator!=(const SimulationOwner& other); @@ -84,11 +152,11 @@ public: private: QUuid _id; // owner - quint64 _expiry; // time when ownership can transition at equal priority - quint64 _pendingBidTimestamp; // time when pending bid was set - quint8 _priority; // priority of current owner - quint8 _pendingBidPriority; // priority at which we'd like to own it - quint8 _pendingState; // NOTHING, TAKE, or RELEASE + uint64_t _expiry; // time when ownership can transition at equal priority + uint64_t _pendingBidTimestamp; // time when pending bid was set + uint8_t _priority; // priority of current owner + uint8_t _pendingBidPriority; // priority at which we'd like to own it + uint8_t _pendingState; // NOTHING, TAKE, or RELEASE }; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 7030a95562..97080d3ca2 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -98,8 +98,6 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_TEXT; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 06b377ee14..efdc84bcd8 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -30,7 +30,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index fa7e5ca38f..32bd2f06ba 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -288,7 +288,7 @@ OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(const OctreeEle int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntityBox); if (childIndex == indexOfChildContainingNewEntity) { - return element->addChildAtIndex(childIndex);; + return element->addChildAtIndex(childIndex); } } } diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 548bca3225..f3159ba3f8 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -83,8 +83,6 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SOURCE_URL; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index dab7cd5e22..1179f22ded 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -29,7 +29,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 4ae020f966..b07d0597bc 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -191,8 +191,6 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 2c6b01fc69..3a9c7cb1e6 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -33,7 +33,6 @@ public: virtual bool setProperties(const EntityItemProperties& properties) override; virtual bool setSubClassProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index b0e2faa600..e8365e38b7 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -566,20 +566,20 @@ glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) { } void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); - unsigned int totalSourceIndices = 0; foreach(const FBXMeshPart& part, extractedMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); } + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + if (!totalSourceIndices) { - qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url; + HIFI_FCDEBUG_ID(modelformat(), repeatMessageID, "buildModelMesh failed -- no indices, url = " << url); return; } if (extractedMesh.vertices.size() == 0) { - qCDebug(modelformat) << "buildModelMesh failed -- no vertices, url = " << url; + HIFI_FCDEBUG_ID(modelformat(), repeatMessageID, "buildModelMesh failed -- no vertices, url = " << url); return; } diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index c1d049f7f3..1c0ad1a85e 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -72,6 +72,10 @@ void GLWidget::createContext() { _context->doneCurrent(); } +void GLWidget::swapBuffers() { + _context->swapBuffers(); +} + bool GLWidget::makeCurrent() { gl::Context::makeCurrent(_context->qglContext(), windowHandle()); return _context->makeCurrent(); diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index 21dffc1b75..a0bf8ea0b0 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -32,6 +32,7 @@ public: void createContext(); bool makeCurrent(); void doneCurrent(); + void swapBuffers(); gl::Context* context() { return _context; } QOpenGLContext* qglContext(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 2089b8a378..9bb02d678d 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -55,6 +55,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_setUniformBuffer), (&::gpu::gl::GLBackend::do_setResourceBuffer), (&::gpu::gl::GLBackend::do_setResourceTexture), + (&::gpu::gl::GLBackend::do_setResourceTextureTable), (&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture), (&::gpu::gl::GLBackend::do_setFramebuffer), diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index f2e2271552..6355137a19 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -40,7 +40,6 @@ #endif - // Let these be configured by the one define picked above #ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE #define GPU_STEREO_DRAWCALL_DOUBLED @@ -102,6 +101,12 @@ public: static const int MAX_NUM_RESOURCE_TEXTURES = 16; size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } + // Texture Tables offers 2 dedicated slot (taken from the ubo slots) + static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2; + static const int RESOURCE_TABLE_TEXTURE_SLOT_OFFSET = TRANSFORM_CAMERA_SLOT + 1; + size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; } + + // Draw Stage virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; @@ -130,6 +135,7 @@ public: // Resource Stage virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final; + virtual void do_setResourceTextureTable(const Batch& batch, size_t paramOffset); virtual void do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) final; // Pipeline Stage @@ -230,6 +236,10 @@ protected: void recycle() const override; + // FIXME instead of a single flag, create a features struct similar to + // https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html + virtual bool supportsBindless() const { return false; } + static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass { false }; int32_t _uboAlignment { 0 }; @@ -389,6 +399,10 @@ protected: virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0; virtual void releaseResourceBuffer(uint32_t slot) = 0; + // Helper function that provides common code used by do_setResourceTexture and + // do_setResourceTextureTable (in non-bindless mode) + void bindResourceTexture(uint32_t slot, const TexturePointer& texture); + // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 237b8bc1e9..58fcc51605 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -1,4 +1,4 @@ -// +// // GLBackendPipeline.cpp // libraries/gpu/src/gpu // @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" +#include + #include "GLShared.h" #include "GLPipeline.h" #include "GLShader.h" @@ -247,8 +249,11 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { return; } - TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + bindResourceTexture(slot, resourceTexture); +} +void GLBackend::bindResourceTexture(uint32_t slot, const TexturePointer& resourceTexture) { if (!resourceTexture) { releaseResourceTexture(slot); return; @@ -306,6 +311,19 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso } } +void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) { + const auto& textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint); + if (!textureTablePointer) { + return; + } + + const auto& textureTable = *textureTablePointer; + const auto& textures = textureTable.getTextures(); + for (GLuint slot = 0; slot < textures.size(); ++slot) { + bindResourceTexture(slot, textures[slot]); + } +} + int GLBackend::ResourceStageState::findEmptyTextureSlot() const { // start from the end of the slots, try to find an empty one that can be used for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) { @@ -315,4 +333,3 @@ int GLBackend::ResourceStageState::findEmptyTextureSlot() const { } return -1; } - diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp index 35dc8a3fbd..0df228ddc4 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp @@ -78,6 +78,11 @@ R"SHADER( #endif }; +// TextureTable specific defines +static const std::string textureTableVersion { + "#extension GL_ARB_bindless_texture : require\n#define GPU_TEXTURE_TABLE_BINDLESS\n" +}; + // Versions specific of the shader static const std::array VERSION_DEFINES { { "", @@ -96,9 +101,9 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co auto& shaderObject = shaderObjects[version]; std::string shaderDefines = getBackendShaderHeader() + "\n" + + (supportsBindless() ? textureTableVersion : "\n") + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version]; - if (handler) { bool retest = true; std::string currentSrc = shaderSource; @@ -120,7 +125,7 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co compilationLogs[version].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, compilationLogs[version].message); } - if (!compilationLogs[version].compiled) { + if (!compilationLogs[version].compiled) { qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[version].message.c_str(); shader.setCompilationLogs(compilationLogs); return nullptr; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp index 99a9afb4c4..4d94f8d8e7 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp @@ -370,7 +370,36 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::COMPRESSED_BC7_SRGBA: result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; - + case gpu::COMPRESSED_ETC2_RGB: + result = GL_COMPRESSED_RGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB: + result = GL_COMPRESSED_SRGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + result = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + result = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGBA: + result = GL_COMPRESSED_RGBA8_ETC2_EAC; + break; + case gpu::COMPRESSED_ETC2_SRGBA: + result = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + break; + case gpu::COMPRESSED_EAC_RED: + result = GL_COMPRESSED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_RED_SIGNED: + result = GL_COMPRESSED_SIGNED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_XY: + result = GL_COMPRESSED_RG11_EAC; + break; + case gpu::COMPRESSED_EAC_XY_SIGNED: + result = GL_COMPRESSED_SIGNED_RG11_EAC; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } @@ -531,6 +560,36 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; + case gpu::COMPRESSED_ETC2_RGB: + texel.internalFormat = GL_COMPRESSED_RGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB: + texel.internalFormat = GL_COMPRESSED_SRGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGBA: + texel.internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; + break; + case gpu::COMPRESSED_ETC2_SRGBA: + texel.internalFormat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + break; + case gpu::COMPRESSED_EAC_RED: + texel.internalFormat = GL_COMPRESSED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_RED_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_XY: + texel.internalFormat = GL_COMPRESSED_RG11_EAC; + break; + case gpu::COMPRESSED_EAC_XY_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_RG11_EAC; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } @@ -895,7 +954,36 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; - + case gpu::COMPRESSED_ETC2_RGB: + texel.internalFormat = GL_COMPRESSED_RGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB: + texel.internalFormat = GL_COMPRESSED_SRGB8_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + texel.internalFormat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + break; + case gpu::COMPRESSED_ETC2_RGBA: + texel.internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; + break; + case gpu::COMPRESSED_ETC2_SRGBA: + texel.internalFormat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + break; + case gpu::COMPRESSED_EAC_RED: + texel.internalFormat = GL_COMPRESSED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_RED_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_R11_EAC; + break; + case gpu::COMPRESSED_EAC_XY: + texel.internalFormat = GL_COMPRESSED_RG11_EAC; + break; + case gpu::COMPRESSED_EAC_XY_SIGNED: + texel.internalFormat = GL_COMPRESSED_SIGNED_RG11_EAC; + break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 3c59781f78..9479321747 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -173,6 +173,8 @@ protected: void makeProgramBindings(ShaderObject& shaderObject) override; int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override; + static bool supportedTextureFormat(const gpu::Element& format); + }; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 61c3da391b..0298b8b892 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -19,6 +19,24 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl41; +bool GL41Backend::supportedTextureFormat(const gpu::Element& format) { + switch (format.getSemantic()) { + case gpu::Semantic::COMPRESSED_ETC2_RGB: + case gpu::Semantic::COMPRESSED_ETC2_SRGB: + case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_RGBA: + case gpu::Semantic::COMPRESSED_ETC2_SRGBA: + case gpu::Semantic::COMPRESSED_EAC_RED: + case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED: + case gpu::Semantic::COMPRESSED_EAC_XY: + case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED: + return false; + default: + return true; + } +} + GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { if (!texturePointer) { return nullptr; @@ -34,6 +52,11 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) { return nullptr; } + // Check whether the texture is in a format we can deal with + if (!supportedTextureFormat(texture.getTexelFormat())) { + return nullptr; + } + GL41Texture* object = Backend::getGPUObject(texture); if (!object) { switch (texture.getUsageType()) { @@ -167,7 +190,6 @@ void GL41Texture::syncSampler() const { glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); @@ -206,9 +228,6 @@ void GL41FixedAllocationTexture::allocateStorage() const { void GL41FixedAllocationTexture::syncSampler() const { Parent::syncSampler(); const Sampler& sampler = _gpuObject.getSampler(); - auto baseMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); - - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip); glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip())); } @@ -610,4 +629,3 @@ GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr& backend GL41ResourceTexture::~GL41ResourceTexture() { } - diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index b90c0b1857..c23a83eaf9 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -16,9 +16,11 @@ #include #include +#include #define INCREMENTAL_TRANSFER 0 #define GPU_SSBO_TRANSFORM_OBJECT 1 +#define GPU_BINDLESS_TEXTURES 0 namespace gpu { namespace gl45 { @@ -31,6 +33,9 @@ class GL45Backend : public GLBackend { friend class Context; public: +#if GPU_BINDLESS_TEXTURES + virtual bool supportsBindless() const override { return true; } +#endif #ifdef GPU_SSBO_TRANSFORM_OBJECT static const GLint TRANSFORM_OBJECT_SLOT { 14 }; // SSBO binding slot @@ -58,8 +63,62 @@ public: void generateMips() const override; Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override; void syncSampler() const override; + +#if GPU_BINDLESS_TEXTURES + bool isBindless() const { + return _bindless.operator bool(); + } + + struct Bindless { + uint64_t handle{ 0 }; + uint32_t minMip{ 0 }; + uint32_t sampler{ 0 }; + + bool operator==(const Bindless& other) const { + return handle == other.handle && minMip == other.minMip && sampler == other.sampler; + } + + bool operator!=(const Bindless& other) const { + return !(*this == other); + } + + operator bool() const { + return handle != 0; + } + }; + + virtual const Bindless& getBindless() const; + void releaseBindless() const; + void recreateBindless() const; + private: + mutable Bindless _bindless; +#endif + + static Sampler getInvalidSampler(); + + // This stores the texture handle (64 bits) in xy, the min mip available in z, and the sampler ID in w + mutable Sampler _cachedSampler{ getInvalidSampler() }; }; +#if GPU_BINDLESS_TEXTURES + class GL45TextureTable : public GLObject { + static GLuint allocate(); + using Parent = GLObject; + public: + using BindlessArray = std::array; + + GL45TextureTable(const std::weak_ptr& backend, const TextureTable& texture); + ~GL45TextureTable(); + + void update(const BindlessArray& newHandles); + + // FIXME instead of making a buffer for each table, there should be a global buffer of all materials + // and we should store an offset into that buffer + BindlessArray _handles; + }; +#endif + + // // Textures that have fixed allocation sizes and cannot be managed at runtime // @@ -74,6 +133,7 @@ public: protected: Size size() const override { return _size; } + void allocateStorage() const; void syncSampler() const override; const Size _size { 0 }; @@ -104,7 +164,6 @@ public: friend class GL45Backend; using PromoteLambda = std::function; - protected: GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture); ~GL45VariableAllocationTexture(); @@ -114,6 +173,9 @@ public: Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override; void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) override; +#if GPU_BINDLESS_TEXTURES + virtual const Bindless& getBindless() const override; +#endif }; class GL45ResourceTexture : public GL45VariableAllocationTexture { @@ -182,6 +244,7 @@ protected: GLuint getQueryID(const QueryPointer& query) override; GLQuery* syncGPUObject(const Query& query) override; + // Draw Stage void do_draw(const Batch& batch, size_t paramOffset) override; void do_drawIndexed(const Batch& batch, size_t paramOffset) override; @@ -213,6 +276,12 @@ protected: // Texture Management Stage void initTextureManagementStage() override; + +#if GPU_BINDLESS_TEXTURES + GL45TextureTable* syncGPUObject(const TextureTablePointer& textureTable); + // Resource stage + void do_setResourceTextureTable(const Batch& batch, size_t paramOffset) override; +#endif }; } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp index c17a4a1524..8de60a7921 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp @@ -165,6 +165,11 @@ void GL45Backend::makeProgramBindings(ShaderObject& shaderObject) { shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT; } + loc = glGetUniformBlockIndex(glprogram, "gpu_resourceTextureTable0"); + if (loc >= 0) { + glUniformBlockBinding(glprogram, loc, RESOURCE_TABLE_TEXTURE_SLOT_OFFSET); + } + (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 5e64e1845e..4d5ffefa67 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -20,14 +20,17 @@ #include #include +#include +#include #include using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -#define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f #define MAX_RESOURCE_TEXTURES_PER_FRAME 2 +#define FORCE_STRICT_TEXTURE 0 +#define ENABLE_SPARSE_TEXTURE 0 GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { if (!texturePointer) { @@ -51,14 +54,18 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { object = new GL45AttachmentTexture(shared_from_this(), texture); break; +#if FORCE_STRICT_TEXTURE + case TextureUsageType::RESOURCE: +#endif case TextureUsageType::STRICT_RESOURCE: qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str(); object = new GL45StrictResourceTexture(shared_from_this(), texture); break; +#if !FORCE_STRICT_TEXTURE case TextureUsageType::RESOURCE: { if (GL45VariableAllocationTexture::_frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME) { -#if 0 +#if ENABLE_SPARSE_TEXTURE if (isTextureManagementSparseEnabled() && GL45Texture::isSparseEligible(texture)) { object = new GL45SparseResourceTexture(shared_from_this(), texture); } else { @@ -76,7 +83,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) { } break; } - +#endif default: Q_UNREACHABLE(); } @@ -114,6 +121,61 @@ void GL45Backend::initTextureManagementStage() { using GL45Texture = GL45Backend::GL45Texture; + +class GLSamplerCache { +public: + GLuint getGLSampler(const Sampler& sampler) { + if (0 == _samplerCache.count(sampler)) { + GLuint result = 0; + glGenSamplers(1, &result); + const auto& fm = GLTexture::FILTER_MODES[sampler.getFilter()]; + glSamplerParameteri(result, GL_TEXTURE_MIN_FILTER, fm.minFilter); + glSamplerParameteri(result, GL_TEXTURE_MAG_FILTER, fm.magFilter); + if (sampler.doComparison()) { + glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); + glSamplerParameteri(result, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); + } else { + glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + + glSamplerParameteri(result, GL_TEXTURE_WRAP_S, GLTexture::WRAP_MODES[sampler.getWrapModeU()]); + glSamplerParameteri(result, GL_TEXTURE_WRAP_T, GLTexture::WRAP_MODES[sampler.getWrapModeV()]); + glSamplerParameteri(result, GL_TEXTURE_WRAP_R, GLTexture::WRAP_MODES[sampler.getWrapModeW()]); + + glSamplerParameterf(result, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + glSamplerParameterfv(result, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + + glSamplerParameterf(result, GL_TEXTURE_MIN_LOD, sampler.getMinMip()); + glSamplerParameterf(result, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + _samplerCache[sampler] = result; + return result; + } + + return _samplerCache[sampler]; + } + + void releaseGLSampler(GLuint sampler) { + // NO OP + } + +private: + std::unordered_map _samplerCache; +}; + +static GLSamplerCache SAMPLER_CACHE; + + +Sampler GL45Texture::getInvalidSampler() { + static Sampler INVALID_SAMPLER; + static std::once_flag once; + std::call_once(once, [] { + Sampler::Desc invalidDesc; + invalidDesc._borderColor = vec4(-1.0f); + INVALID_SAMPLER = Sampler(invalidDesc); + }); + return INVALID_SAMPLER; +} + GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture) : GLTexture(backend, texture, allocate(texture)) { } @@ -121,10 +183,10 @@ GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& GLuint GL45Texture::allocate(const Texture& texture) { GLuint result; glCreateTextures(getGLTextureType(texture), 1, &result); -#ifdef DEBUG - auto source = texture.source(); - glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data()); -#endif + if (::gl::Context::enableDebugLogger()) { + auto source = texture.source(); + glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data()); + } return result; } @@ -144,6 +206,16 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -160,6 +232,16 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: if (glCompressedTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat, @@ -189,8 +271,50 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const return amountCopied; } +#if GPU_BINDLESS_TEXTURES +void GL45Texture::releaseBindless() const { + // Release the old handler + SAMPLER_CACHE.releaseGLSampler(_bindless.sampler); + glMakeTextureHandleNonResidentARB(_bindless.handle); + _bindless = Bindless(); +} + +void GL45Texture::recreateBindless() const { + if (isBindless()) { + releaseBindless(); + } else { + // Once a texture is about to become bindless, it's base mip level MUST be set to 0 + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); + } + + _bindless.sampler = SAMPLER_CACHE.getGLSampler(_cachedSampler); + _bindless.handle = glGetTextureSamplerHandleARB(_id, _bindless.sampler); + glMakeTextureHandleResidentARB(_bindless.handle); +} + +const GL45Texture::Bindless& GL45Texture::getBindless() const { + if (!_bindless) { + recreateBindless(); + } + return _bindless; +} +#endif + + void GL45Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); + if (_cachedSampler == sampler) { + return; + } + + _cachedSampler = sampler; + +#if GPU_BINDLESS_TEXTURES + if (isBindless()) { + recreateBindless(); + return; + } +#endif const auto& fm = FILTER_MODES[sampler.getFilter()]; glTextureParameteri(_id, GL_TEXTURE_MIN_FILTER, fm.minFilter); @@ -214,6 +338,8 @@ void GL45Texture::syncSampler() const { glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); } +// Fixed allocation textures, used for strict resources & framebuffer attachments + using GL45FixedAllocationTexture = GL45Backend::GL45FixedAllocationTexture; GL45FixedAllocationTexture::GL45FixedAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture), _size(texture.evalTotalSize()) { @@ -238,8 +364,6 @@ void GL45FixedAllocationTexture::allocateStorage() const { void GL45FixedAllocationTexture::syncSampler() const { Parent::syncSampler(); const Sampler& sampler = _gpuObject.getSampler(); - auto baseMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip); glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); } @@ -275,6 +399,9 @@ GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr& backend, const TextureTable& textureTable) + : Parent(backend, textureTable, allocate()){ + Backend::setGPUObject(textureTable, this); + // FIXME include these in overall buffer storage reporting + glNamedBufferStorage(_id, sizeof(uvec4) * TextureTable::COUNT, nullptr, GL_DYNAMIC_STORAGE_BIT); +} + +void GL45TextureTable::update(const BindlessArray& handles) { + if (_handles != handles) { + _handles = handles; + // FIXME include these in overall buffer storage reporting + // FIXME use a single shared buffer for bindless data + glNamedBufferSubData(_id, 0, sizeof(GL45Texture::Bindless) * TextureTable::COUNT, &_handles[0]); + } +} + +GL45TextureTable::~GL45TextureTable() { + if (_id) { + auto backend = _backend.lock(); + if (backend) { + // FIXME include these in overall buffer storage reporting + backend->releaseBuffer(_id, 0); + } + } +} + +GL45TextureTable* GL45Backend::syncGPUObject(const TextureTablePointer& textureTablePointer) { + const auto& textureTable = *textureTablePointer; + + // Find the target handles + auto defaultTextures = gpu::TextureTable::getDefault()->getTextures(); + auto textures = textureTable.getTextures(); + GL45TextureTable::BindlessArray handles{}; + for (size_t i = 0; i < textures.size(); ++i) { + auto texture = textures[i]; + if (!texture) { + texture = defaultTextures[i]; + } + if (!texture) { + continue; + } + // FIXME what if we have a non-transferrable texture here? + auto gltexture = (GL45Texture*)syncGPUObject(texture); + if (!gltexture) { + continue; + } + handles[i] = gltexture->getBindless(); + } + + // If the object hasn't been created, or the object definition is out of date, drop and re-create + GL45TextureTable* object = Backend::getGPUObject(textureTable); + + if (!object) { + object = new GL45TextureTable(shared_from_this(), textureTable); + } + + object->update(handles); + + return object; +} + +void GL45Backend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) { + auto textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint); + if (!textureTablePointer) { + return; + } + + auto slot = batch._params[paramOffset + 1]._uint; + GL45TextureTable* glTextureTable = syncGPUObject(textureTablePointer); + if (glTextureTable) { + glBindBufferBase(GL_UNIFORM_BUFFER, slot + GLBackend::RESOURCE_TABLE_TEXTURE_SLOT_OFFSET, glTextureTable->_id); + } +} +#endif diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index d2d7b2bf55..08d077605d 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -27,6 +27,7 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; +using GL45Texture = GL45Backend::GL45Texture; using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture; GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GL45Texture(backend, texture) { @@ -40,6 +41,17 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() { Backend::textureResourcePopulatedGPUMemSize.update(_populatedSize, 0); } +#if GPU_BINDLESS_TEXTURES +const GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const { + // The parent call may re-initialize the _bindless member, so we need to call it first + const auto& result = Parent::getBindless(); + // Make sure the referenced structure has the correct minimum available mip value + _bindless.minMip = _populatedMip - _allocatedMip; + // Now return the result + return result; +} +#endif + Size GL45VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const { Size amountCopied = 0; amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer); @@ -127,7 +139,13 @@ Size GL45ResourceTexture::copyMipsFromTexture() { void GL45ResourceTexture::syncSampler() const { Parent::syncSampler(); +#if GPU_BINDLESS_TEXTURES + if (!isBindless()) { + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); + } +#else glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); +#endif } void GL45ResourceTexture::promote() { @@ -137,6 +155,13 @@ void GL45ResourceTexture::promote() { uint16_t targetAllocatedMip = _allocatedMip - std::min(_allocatedMip, 2); targetAllocatedMip = std::max(_minAllocatedMip, targetAllocatedMip); +#if GPU_BINDLESS_TEXTURES + bool bindless = isBindless(); + if (bindless) { + releaseBindless(); + } +#endif + GLuint oldId = _id; auto oldSize = _size; uint16_t oldAllocatedMip = _allocatedMip; @@ -150,10 +175,17 @@ void GL45ResourceTexture::promote() { // copy pre-existing mips copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); +#if GPU_BINDLESS_TEXTURES + if (bindless) { + getBindless(); + } +#endif + // destroy the old texture glDeleteTextures(1, &oldId); // Update sampler + _cachedSampler = getInvalidSampler(); syncSampler(); // update the memory usage @@ -170,6 +202,13 @@ void GL45ResourceTexture::demote() { auto oldSize = _size; auto oldPopulatedMip = _populatedMip; +#if GPU_BINDLESS_TEXTURES + bool bindless = isBindless(); + if (bindless) { + releaseBindless(); + } +#endif + // allocate new texture const_cast(_id) = allocate(_gpuObject); uint16_t oldAllocatedMip = _allocatedMip; @@ -179,10 +218,17 @@ void GL45ResourceTexture::demote() { // copy pre-existing mips copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); +#if GPU_BINDLESS_TEXTURES + if (bindless) { + getBindless(); + } +#endif + // destroy the old texture glDeleteTextures(1, &oldId); // Update sampler + _cachedSampler = getInvalidSampler(); syncSampler(); // update the memory usage diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index 6bc55a23d4..2009dc5dc9 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -20,9 +20,21 @@ using namespace gpu::gl; using namespace gpu::gles; bool GLESBackend::supportedTextureFormat(const gpu::Element& format) { - // FIXME distinguish between GLES and GL compressed formats after support - // for the former is added to gpu::Element - return !format.isCompressed(); + switch (format.getSemantic()) { + case gpu::Semantic::COMPRESSED_ETC2_RGB: + case gpu::Semantic::COMPRESSED_ETC2_SRGB: + case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA: + case gpu::Semantic::COMPRESSED_ETC2_RGBA: + case gpu::Semantic::COMPRESSED_ETC2_SRGBA: + case gpu::Semantic::COMPRESSED_EAC_RED: + case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED: + case gpu::Semantic::COMPRESSED_EAC_XY: + case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED: + return true; + default: + return !format.isCompressed(); + } } GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) { @@ -231,6 +243,29 @@ GLESFixedAllocationTexture::GLESFixedAllocationTexture(const std::weak_ptr= _items.size()) { - return Data(); + static const Data EMPTY; + return EMPTY; } return (_items.data() + offset)->_data; } @@ -424,6 +427,7 @@ public: typedef Cache::Vector BufferCaches; typedef Cache::Vector TextureCaches; + typedef Cache::Vector TextureTableCaches; typedef Cache::Vector StreamFormatCaches; typedef Cache::Vector TransformCaches; typedef Cache::Vector PipelineCaches; @@ -479,6 +483,7 @@ public: BufferCaches _buffers; TextureCaches _textures; + TextureTableCaches _textureTables; StreamFormatCaches _streamFormats; TransformCaches _transforms; PipelineCaches _pipelines; diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index e1b68c88ca..7dc6965076 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -129,6 +129,7 @@ void Context::executeFrame(const FramePointer& frame) const { } bool Context::makeProgram(Shader& shader, const Shader::BindingSet& bindings, const Shader::CompilationHandler& handler) { + PROFILE_RANGE_EX(app, "makeProgram", 0xff4040c0, shader.getID()); // If we're running in another DLL context, we need to fetch the program callback out of the application // FIXME find a way to do this without reliance on Qt app properties if (!_makeProgramCallback) { diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index c4b6b39723..7e277ae488 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -21,13 +21,24 @@ const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA }; const Element Element::COLOR_RGBA_2{ VEC4, NUINT2, RGBA }; -const Element Element::COLOR_COMPRESSED_RED{ TILE4x4, COMPRESSED, COMPRESSED_BC4_RED }; -const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB }; -const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; -const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; -const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; -const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; -const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; +const Element Element::COLOR_COMPRESSED_BCX_RED { TILE4x4, COMPRESSED, COMPRESSED_BC4_RED }; +const Element Element::COLOR_COMPRESSED_BCX_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB }; +const Element Element::COLOR_COMPRESSED_BCX_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; +const Element Element::COLOR_COMPRESSED_BCX_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; +const Element Element::COLOR_COMPRESSED_BCX_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; +const Element Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_BCX_HDR_RGB { TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; + +const Element Element::COLOR_COMPRESSED_ETC2_RGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB }; +const Element Element::COLOR_COMPRESSED_ETC2_SRGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB }; +const Element Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA }; +const Element Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA }; +const Element Element::COLOR_COMPRESSED_ETC2_RGBA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGBA }; +const Element Element::COLOR_COMPRESSED_ETC2_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGBA }; +const Element Element::COLOR_COMPRESSED_EAC_RED { TILE4x4, COMPRESSED, COMPRESSED_EAC_RED }; +const Element Element::COLOR_COMPRESSED_EAC_RED_SIGNED { TILE4x4, COMPRESSED, COMPRESSED_EAC_RED_SIGNED }; +const Element Element::COLOR_COMPRESSED_EAC_XY { TILE4x4, COMPRESSED, COMPRESSED_EAC_XY }; +const Element Element::COLOR_COMPRESSED_EAC_XY_SIGNED { TILE4x4, COMPRESSED, COMPRESSED_EAC_XY_SIGNED }; const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 17102d0415..d4e4a89636 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -194,6 +194,17 @@ enum Semantic : uint8_t { COMPRESSED_BC6_RGB, COMPRESSED_BC7_SRGBA, + COMPRESSED_ETC2_RGB, + COMPRESSED_ETC2_SRGB, + COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, + COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, + COMPRESSED_ETC2_RGBA, + COMPRESSED_ETC2_SRGBA, + COMPRESSED_EAC_RED, + COMPRESSED_EAC_RED_SIGNED, + COMPRESSED_EAC_XY, + COMPRESSED_EAC_XY_SIGNED, + _LAST_COMPRESSED, R11G11B10, @@ -249,6 +260,17 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = { 16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes 16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes + 8, //COMPRESSED_ETC2_RGB, + 8, //COMPRESSED_ETC2_SRGB, + 8, //COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, + 8, //COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, + 16, //COMPRESSED_ETC2_RGBA, + 16, //COMPRESSED_ETC2_SRGBA, + 8, //COMPRESSED_EAC_RED, + 8, //COMPRESSED_EAC_RED_SIGNED, + 16, //COMPRESSED_EAC_XY, + 16, //COMPRESSED_EAC_XY_SIGNED, + 1, //_LAST_COMPRESSED, 1, //R11G11B10, @@ -316,13 +338,23 @@ public: static const Element COLOR_RGBA_2; static const Element COLOR_R11G11B10; static const Element COLOR_RGB9E5; - static const Element COLOR_COMPRESSED_RED; - static const Element COLOR_COMPRESSED_SRGB; - static const Element COLOR_COMPRESSED_SRGBA_MASK; - static const Element COLOR_COMPRESSED_SRGBA; - static const Element COLOR_COMPRESSED_XY; - static const Element COLOR_COMPRESSED_SRGBA_HIGH; - static const Element COLOR_COMPRESSED_HDR_RGB; + static const Element COLOR_COMPRESSED_BCX_RED; + static const Element COLOR_COMPRESSED_BCX_SRGB; + static const Element COLOR_COMPRESSED_BCX_SRGBA_MASK; + static const Element COLOR_COMPRESSED_BCX_SRGBA; + static const Element COLOR_COMPRESSED_BCX_XY; + static const Element COLOR_COMPRESSED_BCX_SRGBA_HIGH; + static const Element COLOR_COMPRESSED_BCX_HDR_RGB; + static const Element COLOR_COMPRESSED_ETC2_RGB; + static const Element COLOR_COMPRESSED_ETC2_SRGB; + static const Element COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA; + static const Element COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA; + static const Element COLOR_COMPRESSED_ETC2_RGBA; + static const Element COLOR_COMPRESSED_ETC2_SRGBA; + static const Element COLOR_COMPRESSED_EAC_RED; + static const Element COLOR_COMPRESSED_EAC_RED_SIGNED; + static const Element COLOR_COMPRESSED_EAC_XY; + static const Element COLOR_COMPRESSED_EAC_XY_SIGNED; static const Element VEC2NU8_XY; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 8100efafaf..987788c29b 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -91,6 +91,8 @@ namespace gpu { using Textures = std::vector; class TextureView; using TextureViews = std::vector; + class TextureTable; + using TextureTablePointer = std::shared_ptr; struct StereoState { bool isStereo() const { diff --git a/libraries/gpu/src/gpu/Shader.cpp b/libraries/gpu/src/gpu/Shader.cpp index aa7898569b..b539a84925 100755 --- a/libraries/gpu/src/gpu/Shader.cpp +++ b/libraries/gpu/src/gpu/Shader.cpp @@ -75,6 +75,7 @@ Shader::Pointer Shader::createGeometry(const Source& source) { } ShaderPointer Shader::createOrReuseProgramShader(Type type, const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { + PROFILE_RANGE(app, "createOrReuseProgramShader"); ProgramMapKey key(0); if (vertexShader && vertexShader->getType() == VERTEX) { diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index a03b894e0d..4d82aba595 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -17,8 +17,10 @@ #include #include +#include #include #include +#include #include "Forward.h" #include "Resource.h" #include "Metric.h" @@ -132,6 +134,19 @@ public: Desc() {} Desc(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _filter(filter), _wrapModeU(wrap), _wrapModeV(wrap), _wrapModeW(wrap) {} + + bool operator==(const Desc& other) const { + return _borderColor == other._borderColor && + _maxAnisotropy == other._maxAnisotropy && + _filter == other._filter && + _comparisonFunc == other._comparisonFunc && + _wrapModeU == other._wrapModeU && + _wrapModeV == other._wrapModeV && + _wrapModeW == other._wrapModeW && + _mipOffset == other._mipOffset && + _minMip == other._minMip && + _maxMip == other._maxMip; + } }; Sampler() {} @@ -156,6 +171,13 @@ public: uint8 getMaxMip() const { return _desc._maxMip; } const Desc& getDesc() const { return _desc; } + + bool operator==(const Sampler& other) const { + return _desc == other._desc; + } + bool operator!=(const Sampler& other) const { + return !(*this == other); + } protected: Desc _desc; }; @@ -666,6 +688,17 @@ typedef std::shared_ptr< TextureSource > TextureSourcePointer; }; +namespace std { + template<> struct hash { + size_t operator()(const gpu::Sampler& sampler) const noexcept { + size_t result = 0; + const auto& desc = sampler.getDesc(); + hash_combine(result, desc._comparisonFunc, desc._filter, desc._maxAnisotropy, desc._maxMip, desc._minMip, desc._wrapModeU, desc._wrapModeV, desc._wrapModeW); + return result; + } + }; +} + Q_DECLARE_METATYPE(gpu::TexturePointer) #endif diff --git a/libraries/gpu/src/gpu/TextureTable.cpp b/libraries/gpu/src/gpu/TextureTable.cpp new file mode 100644 index 0000000000..0c3a43808b --- /dev/null +++ b/libraries/gpu/src/gpu/TextureTable.cpp @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2017/01/25 +// Copyright 2013-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 "TextureTable.h" +#include "Texture.h" + +#include +using namespace gpu; + + +const size_t TextureTable::COUNT{ TEXTURE_TABLE_COUNT }; + +TextureTable::TextureTable() { } + +TextureTable::TextureTable(const std::initializer_list& textures) { + auto max = std::min(COUNT, textures.size()); + auto itr = textures.begin(); + size_t index = 0; + while (itr != textures.end() && index < max) { + setTexture(index, *itr); + ++index; + } +} + +TextureTable::TextureTable(const Array& textures) : _textures(textures) { +} + +void TextureTable::setTexture(size_t index, const TexturePointer& texturePointer) { + if (index >= COUNT || _textures[index] == texturePointer) { + return; + } + { + Lock lock(_mutex); + ++_stamp; + _textures[index] = texturePointer; + } +} + +void TextureTable::setTexture(size_t index, const TextureView& textureView) { + setTexture(index, textureView._texture); +} + +TextureTable::Array TextureTable::getTextures() const { + Array result; + { + Lock lock(_mutex); + result = _textures; + } + return result; +} diff --git a/libraries/gpu/src/gpu/TextureTable.h b/libraries/gpu/src/gpu/TextureTable.h new file mode 100644 index 0000000000..794b8535d0 --- /dev/null +++ b/libraries/gpu/src/gpu/TextureTable.h @@ -0,0 +1,44 @@ +// +// Created by Bradley Austin Davis on 2017/01/25 +// Copyright 2013-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_gpu_TextureTable_h +#define hifi_gpu_TextureTable_h + +#include "Forward.h" + +#include + +#define TEXTURE_TABLE_COUNT 8 + +namespace gpu { + +class TextureTable { +public: + static const size_t COUNT; + using Array = std::array; + TextureTable(); + TextureTable(const std::initializer_list& textures); + TextureTable(const Array& textures); + + // Only for gpu::Context + const GPUObjectPointer gpuObject{}; + + void setTexture(size_t index, const TexturePointer& texturePointer); + void setTexture(size_t index, const TextureView& texturePointer); + + Array getTextures() const; + Stamp getStamp() const { return _stamp; } + +private: + mutable Mutex _mutex; + Array _textures; + Stamp _stamp{ 1 }; +}; + +} + +#endif diff --git a/libraries/gpu/src/gpu/TextureTable.slh b/libraries/gpu/src/gpu/TextureTable.slh new file mode 100644 index 0000000000..f2d89e753b --- /dev/null +++ b/libraries/gpu/src/gpu/TextureTable.slh @@ -0,0 +1,38 @@ + +<@if not GPU_TEXTURE_TABLE_SLH@> +<@def GPU_TEXTURE_TABLE_SLH@> + +#ifdef GPU_TEXTURE_TABLE_BINDLESS +#define GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES 8 + +struct GPUTextureTable { + uvec4 _textures[GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES]; +}; + +#define TextureTable(index, name) layout (std140) uniform gpu_resourceTextureTable##index { GPUTextureTable name; } + +#define tableTex(name, slot) sampler2D(name._textures[slot].xy) +#define tableTexMinLod(name, slot) float(name._textures[slot].z) + +#define tableTexValue(name, slot, uv) \ + tableTexValueLod(tableTex(matTex, albedoMap), tableTexMinLod(matTex, albedoMap), uv) + +vec4 tableTexValueLod(sampler2D sampler, float minLod, vec2 uv) { + float queryLod = textureQueryLod(sampler, uv).x; + queryLod = max(minLod, queryLod); + return textureLod(sampler, uv, queryLod); +} + +#else + +#endif + +<@endif@> diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 754bdca445..5e354c0a5c 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -504,6 +504,8 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { gpuktxKeyValue._usage = Texture::Usage::Builder().withColor().withAlpha().build(); } + auto samplerDesc = gpuktxKeyValue._samplerDesc; + samplerDesc._maxMip = gpu::Sampler::MAX_MIP_LEVEL; auto texture = create(gpuktxKeyValue._usageType, type, texelFormat, @@ -513,7 +515,7 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { 1, // num Samples header.getNumberOfSlices(), header.getNumberOfLevels(), - gpuktxKeyValue._samplerDesc); + samplerDesc); texture->setUsage(gpuktxKeyValue._usage); // Assing the mips availables @@ -572,20 +574,40 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RED, ktx::GLInternalFormat::R8, ktx::GLBaseInternalFormat::RED); } else if (texelFormat == Format::VEC2NU8_XY && mipFormat == Format::VEC2NU8_XY) { header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RG, ktx::GLInternalFormat::RG8, ktx::GLBaseInternalFormat::RG); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGB && mipFormat == Format::COLOR_COMPRESSED_SRGB) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGB && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGB) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGB); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_SRGBA_MASK) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_MASK) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGBA); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA && mipFormat == Format::COLOR_COMPRESSED_SRGBA) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, ktx::GLBaseInternalFormat::RGBA); - } else if (texelFormat == Format::COLOR_COMPRESSED_RED && mipFormat == Format::COLOR_COMPRESSED_RED) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_RED && mipFormat == Format::COLOR_COMPRESSED_BCX_RED) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RED_RGTC1, ktx::GLBaseInternalFormat::RED); - } else if (texelFormat == Format::COLOR_COMPRESSED_XY && mipFormat == Format::COLOR_COMPRESSED_XY) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_XY && mipFormat == Format::COLOR_COMPRESSED_BCX_XY) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG); - } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA); - } else if (texelFormat == Format::COLOR_COMPRESSED_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_HDR_RGB) { + } else if (texelFormat == Format::COLOR_COMPRESSED_BCX_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_BCX_HDR_RGB) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGB && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGB && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGBA && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGBA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGBA && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGBA) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_RED && mipFormat == Format::COLOR_COMPRESSED_EAC_RED) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_R11_EAC, ktx::GLBaseInternalFormat::RED); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_RED_SIGNED && mipFormat == Format::COLOR_COMPRESSED_EAC_RED_SIGNED) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC, ktx::GLBaseInternalFormat::RED); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_XY && mipFormat == Format::COLOR_COMPRESSED_EAC_XY) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG11_EAC, ktx::GLBaseInternalFormat::RG); + } else if (texelFormat == Format::COLOR_COMPRESSED_EAC_XY_SIGNED && mipFormat == Format::COLOR_COMPRESSED_EAC_XY_SIGNED) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC, ktx::GLBaseInternalFormat::RG); } else if (texelFormat == Format::COLOR_RGB9E5 && mipFormat == Format::COLOR_RGB9E5) { header.setUncompressed(ktx::GLType::UNSIGNED_INT_5_9_9_9_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::RGB9_E5, ktx::GLBaseInternalFormat::RGB); } else if (texelFormat == Format::COLOR_R11G11B10 && mipFormat == Format::COLOR_R11G11B10) { @@ -640,31 +662,45 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E texelFormat = Format::COLOR_RGB9E5; } else if (header.isCompressed()) { if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) { - mipFormat = Format::COLOR_COMPRESSED_SRGB; - texelFormat = Format::COLOR_COMPRESSED_SRGB; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGB; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) { - mipFormat = Format::COLOR_COMPRESSED_SRGBA_MASK; - texelFormat = Format::COLOR_COMPRESSED_SRGBA_MASK; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) { - mipFormat = Format::COLOR_COMPRESSED_SRGBA; - texelFormat = Format::COLOR_COMPRESSED_SRGBA; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) { - mipFormat = Format::COLOR_COMPRESSED_RED; - texelFormat = Format::COLOR_COMPRESSED_RED; + texelFormat = Format::COLOR_COMPRESSED_BCX_RED; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) { - mipFormat = Format::COLOR_COMPRESSED_XY; - texelFormat = Format::COLOR_COMPRESSED_XY; + texelFormat = Format::COLOR_COMPRESSED_BCX_XY; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { - mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; - texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; + texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH; } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) { - mipFormat = Format::COLOR_COMPRESSED_HDR_RGB; - texelFormat = Format::COLOR_COMPRESSED_HDR_RGB; + texelFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_RGBA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) { + texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_R11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_RED; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_XY; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) { + texelFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED; } else { return false; } + mipFormat = texelFormat; } else { return false; } return true; -} +} \ No newline at end of file diff --git a/libraries/graphics/src/graphics/GpuHelpers.cpp b/libraries/graphics/src/graphics/GpuHelpers.cpp index 0c3bd945e1..b864b0f040 100644 --- a/libraries/graphics/src/graphics/GpuHelpers.cpp +++ b/libraries/graphics/src/graphics/GpuHelpers.cpp @@ -87,6 +87,17 @@ namespace gpu { { Semantic::COMPRESSED_BC6_RGB, "compressed_bc6_rgb" }, { Semantic::COMPRESSED_BC7_SRGBA, "compressed_bc7_srgba" }, + { Semantic::COMPRESSED_ETC2_RGB, "compressed_etc2_rgb" }, + { Semantic::COMPRESSED_ETC2_SRGB, "compressed_etc2_srgb" }, + { Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, "compressed_etc2_rgb_punchthrough_alpha" }, + { Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, "compressed_etc2_srgb_punchthrough_alpha" }, + { Semantic::COMPRESSED_ETC2_RGBA, "compressed_etc2_rgba" }, + { Semantic::COMPRESSED_ETC2_SRGBA, "compressed_etc2_srgba" }, + { Semantic::COMPRESSED_EAC_RED, "compressed_eac_red" }, + { Semantic::COMPRESSED_EAC_RED_SIGNED, "compressed_eac_red_signed" }, + { Semantic::COMPRESSED_EAC_XY, "compressed_eac_xy" }, + { Semantic::COMPRESSED_EAC_XY_SIGNED, "compressed_eac_xy_signed" }, + { Semantic::_LAST_COMPRESSED, "_last_compressed" }, { Semantic::R11G11B10, "r11g11b10" }, diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index f9e83a0324..fd3f12e865 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -20,6 +20,7 @@ #include #include +#include class Transform; @@ -361,6 +362,8 @@ public: const std::string& getModel() const { return _model; } void setModel(const std::string& model) { _model = model; } + const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } + protected: std::string _name { "" }; @@ -368,6 +371,7 @@ private: mutable MaterialKey _key; mutable UniformBufferView _schemaBuffer; mutable UniformBufferView _texMapArrayBuffer; + mutable gpu::TextureTablePointer _textureTable{ std::make_shared() }; TextureMaps _textureMaps; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 696e311495..c1c07838c7 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -466,7 +466,8 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomicgetStoredMipFormat(); - if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGB) { + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { compressionOptions.setFormat(nvtt::Format_BC1); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_MASK) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { alphaMode = nvtt::AlphaMode_Transparency; compressionOptions.setFormat(nvtt::Format_BC1a); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { alphaMode = nvtt::AlphaMode_Transparency; compressionOptions.setFormat(nvtt::Format_BC3); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_RED) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { compressionOptions.setFormat(nvtt::Format_BC4); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_XY) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { compressionOptions.setFormat(nvtt::Format_BC5); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH) { + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { alphaMode = nvtt::AlphaMode_Transparency; compressionOptions.setFormat(nvtt::Format_BC7); } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { @@ -736,13 +738,21 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma gpu::Element formatMip; gpu::Element formatGPU; if (isColorTexturesCompressionEnabled()) { +#ifndef USE_GLES if (validAlpha) { // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; } +#else + if (validAlpha) { + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; + } +#endif formatMip = formatGPU; } else { #ifdef USE_GLES @@ -869,8 +879,12 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr gpu::Element formatMip = gpu::Element::VEC2NU8_XY; gpu::Element formatGPU = gpu::Element::VEC2NU8_XY; if (isNormalTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_XY; - formatGPU = gpu::Element::COLOR_COMPRESSED_XY; +#ifndef USE_GLES + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; +#else + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; +#endif + formatMip = formatGPU; } theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); @@ -903,8 +917,12 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (isGrayscaleTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_RED; - formatGPU = gpu::Element::COLOR_COMPRESSED_RED; +#ifndef USE_GLES + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; +#else + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; +#endif + formatMip = formatGPU; } else { formatMip = gpu::Element::COLOR_R_8; formatGPU = gpu::Element::COLOR_R_8; @@ -1271,8 +1289,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + // TODO: gles: pick HDR ETC format + formatMip = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; } else { formatMip = HDR_FORMAT; formatGPU = HDR_FORMAT; diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 0a28368e9e..957104bd30 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -41,6 +41,7 @@ bool TouchscreenVirtualPadDevice::isSupported() const { void TouchscreenVirtualPadDevice::init() { _fixedPosition = true; // This should be config + _viewTouchUpdateCount = 0; QScreen* eventScreen = qApp->primaryScreen(); if (_screenDPIProvided != eventScreen->physicalDotsPerInch()) { @@ -50,12 +51,14 @@ void TouchscreenVirtualPadDevice::init() { _screenDPIProvided = eventScreen->physicalDotsPerInch(); _screenDPI = eventScreen->physicalDotsPerInch(); - _fixedRadius = _screenDPI * 0.5f * VirtualPad::Manager::PIXEL_SIZE / VirtualPad::Manager::DPI; - _fixedRadiusForCalc = _fixedRadius - _screenDPI * VirtualPad::Manager::STICK_RADIUS / VirtualPad::Manager::DPI; + _fixedRadius = _screenDPI * 0.5f * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI; + _fixedRadiusForCalc = _fixedRadius - _screenDPI * VirtualPad::Manager::STICK_RADIUS_PIXELS / VirtualPad::Manager::DPI; + + _jumpButtonRadius = _screenDPI * VirtualPad::Manager::JUMP_BTN_TRIMMED_RADIUS_PIXELS / VirtualPad::Manager::DPI; } auto& virtualPadManager = VirtualPad::Manager::instance(); - setupFixedCenter(virtualPadManager, true); + setupControlsPositions(virtualPadManager, true); if (_fixedPosition) { virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled @@ -64,19 +67,23 @@ void TouchscreenVirtualPadDevice::init() { KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin } -void TouchscreenVirtualPadDevice::setupFixedCenter(VirtualPad::Manager& virtualPadManager, bool force) { - if (!_fixedPosition) return; - - //auto& virtualPadManager = VirtualPad::Manager::instance(); +void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& virtualPadManager, bool force) { if (_extraBottomMargin == virtualPadManager.extraBottomMargin() && !force) return; // Our only criteria to decide a center change is the bottom margin - _extraBottomMargin = virtualPadManager.extraBottomMargin(); - float margin = _screenDPI * VirtualPad::Manager::BASE_MARGIN / VirtualPad::Manager::DPI; QScreen* eventScreen = qApp->primaryScreen(); // do not call every time - _fixedCenterPosition = glm::vec2( _fixedRadius + margin, eventScreen->size().height() - margin - _fixedRadius - _extraBottomMargin); + _extraBottomMargin = virtualPadManager.extraBottomMargin(); + // Movement stick + float margin = _screenDPI * VirtualPad::Manager::BASE_MARGIN_PIXELS / VirtualPad::Manager::DPI; + _fixedCenterPosition = glm::vec2( _fixedRadius + margin, eventScreen->size().height() - margin - _fixedRadius - _extraBottomMargin); _moveRefTouchPoint = _fixedCenterPosition; virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); + + // Jump button + float leftMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_LEFT_MARGIN_PIXELS / VirtualPad::Manager::DPI; + float bottomMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS/ VirtualPad::Manager::DPI; + _jumpButtonPosition = glm::vec2( _jumpButtonRadius + leftMargin, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin); + virtualPadManager.setJumpButtonPosition(_jumpButtonPosition); } float clip(float n, float lower, float upper) { @@ -131,22 +138,15 @@ void TouchscreenVirtualPadDevice::processInputDeviceForMove(VirtualPad::Manager& } void TouchscreenVirtualPadDevice::processInputDeviceForView() { - float rightDistanceScaleX, rightDistanceScaleY; - rightDistanceScaleX = (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _screenDPIScale.x; - rightDistanceScaleY = (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _screenDPIScale.y; - - rightDistanceScaleX = clip(rightDistanceScaleX, -_viewStickRadiusInches, _viewStickRadiusInches); - rightDistanceScaleY = clip(rightDistanceScaleY, -_viewStickRadiusInches, _viewStickRadiusInches); - - // NOW BETWEEN -1 1 - rightDistanceScaleX /= _viewStickRadiusInches; - rightDistanceScaleY /= _viewStickRadiusInches; - - _inputDevice->_axisStateMap[controller::RX] = rightDistanceScaleX; - _inputDevice->_axisStateMap[controller::RY] = rightDistanceScaleY; + // We use average across how many times we've got touchUpdate events. + // Using the average instead of the full deltaX and deltaY, makes deltaTime in MyAvatar dont't accelerate rotation when there is a low touchUpdate rate (heavier domains). + // (Because it multiplies this input value by deltaTime (with a coefficient)). + _inputDevice->_axisStateMap[controller::RX] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _viewTouchUpdateCount; + _inputDevice->_axisStateMap[controller::RY] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _viewTouchUpdateCount; // after use, save last touch point as ref _viewRefTouchPoint = _viewCurrentTouchPoint; + _viewTouchUpdateCount = 0; } void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { @@ -156,7 +156,7 @@ void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller }); auto& virtualPadManager = VirtualPad::Manager::instance(); - setupFixedCenter(virtualPadManager); + setupControlsPositions(virtualPadManager); if (_moveHasValidTouch) { processInputDeviceForMove(virtualPadManager); @@ -221,6 +221,7 @@ void TouchscreenVirtualPadDevice::touchEndEvent(const QTouchEvent* event) { if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { moveTouchEnd(); viewTouchEnd(); + jumpTouchEnd(); return; } // touch end here is a big reset -> resets both pads @@ -229,7 +230,9 @@ void TouchscreenVirtualPadDevice::touchEndEvent(const QTouchEvent* event) { debugPoints(event, " END ----------------"); moveTouchEnd(); viewTouchEnd(); + jumpTouchEnd(); _inputDevice->_axisStateMap.clear(); + _inputDevice->_buttonPressedMap.clear(); } void TouchscreenVirtualPadDevice::processUnusedTouches(std::map unusedTouchesInEvent) { @@ -263,9 +266,11 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { const QList& tPoints = event->touchPoints(); bool moveTouchFound = false; bool viewTouchFound = false; + bool jumpTouchFound = false; int idxMoveStartingPointCandidate = -1; int idxViewStartingPointCandidate = -1; + int idxJumpStartingPointCandidate = -1; glm::vec2 thisPoint; int thisPointId; @@ -290,6 +295,13 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { continue; } + if (!jumpTouchFound && _jumpHasValidTouch && _jumpCurrentTouchId == thisPointId) { + // valid if it's an ongoing touch + jumpTouchFound = true; + jumpTouchUpdate(thisPoint); + continue; + } + if (!moveTouchFound && idxMoveStartingPointCandidate == -1 && moveTouchBeginIsValid(thisPoint) && (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == MOVE )) { idxMoveStartingPointCandidate = i; @@ -302,8 +314,16 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { continue; } + if (!jumpTouchFound && idxJumpStartingPointCandidate == -1 && jumpTouchBeginIsValid(thisPoint) && + (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == JUMP )) { + idxJumpStartingPointCandidate = i; + continue; + } + if (moveTouchBeginIsValid(thisPoint)) { unusedTouchesInEvent[thisPointId] = MOVE; + } else if (jumpTouchBeginIsValid(thisPoint)) { + unusedTouchesInEvent[thisPointId] = JUMP; } else if (viewTouchBeginIsValid(thisPoint)) { unusedTouchesInEvent[thisPointId] = VIEW; } @@ -330,23 +350,58 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { viewTouchEnd(); } } + if (!jumpTouchFound) { + if (idxJumpStartingPointCandidate != -1) { + _jumpCurrentTouchId = tPoints[idxJumpStartingPointCandidate].id(); + _unusedTouches.erase(_jumpCurrentTouchId); + jumpTouchBegin(thisPoint); + } else { + if (_jumpHasValidTouch) { + jumpTouchEnd(); + } + } + } } bool TouchscreenVirtualPadDevice::viewTouchBeginIsValid(glm::vec2 touchPoint) { - return !moveTouchBeginIsValid(touchPoint); + return !moveTouchBeginIsValid(touchPoint) && !jumpTouchBeginIsValid(touchPoint); } bool TouchscreenVirtualPadDevice::moveTouchBeginIsValid(glm::vec2 touchPoint) { if (_fixedPosition) { // inside circle - return pow(touchPoint.x - _fixedCenterPosition.x,2.0) + pow(touchPoint.y - _fixedCenterPosition.y, 2.0) < pow(_fixedRadius, 2.0); + return glm::distance2(touchPoint, _fixedCenterPosition) < _fixedRadius * _fixedRadius; } else { // left side return touchPoint.x < _screenWidthCenter; } } +bool TouchscreenVirtualPadDevice::jumpTouchBeginIsValid(glm::vec2 touchPoint) { + // position of button and boundaries + return glm::distance2(touchPoint, _jumpButtonPosition) < _jumpButtonRadius * _jumpButtonRadius; +} + +void TouchscreenVirtualPadDevice::jumpTouchBegin(glm::vec2 touchPoint) { + auto& virtualPadManager = VirtualPad::Manager::instance(); + if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + _jumpHasValidTouch = true; + + _inputDevice->_buttonPressedMap.insert(TouchButtonChannel::JUMP_BUTTON_PRESS); + } +} + +void TouchscreenVirtualPadDevice::jumpTouchUpdate(glm::vec2 touchPoint) {} + +void TouchscreenVirtualPadDevice::jumpTouchEnd() { + if (_jumpHasValidTouch) { + _jumpHasValidTouch = false; + + _inputDevice->_buttonPressedMap.erase(TouchButtonChannel::JUMP_BUTTON_PRESS); + } +} + void TouchscreenVirtualPadDevice::moveTouchBegin(glm::vec2 touchPoint) { auto& virtualPadManager = VirtualPad::Manager::instance(); if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { @@ -376,12 +431,14 @@ void TouchscreenVirtualPadDevice::viewTouchBegin(glm::vec2 touchPoint) { if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { _viewRefTouchPoint = touchPoint; _viewCurrentTouchPoint = touchPoint; + _viewTouchUpdateCount++; _viewHasValidTouch = true; } } void TouchscreenVirtualPadDevice::viewTouchUpdate(glm::vec2 touchPoint) { _viewCurrentTouchPoint = touchPoint; + _viewTouchUpdateCount++; } void TouchscreenVirtualPadDevice::viewTouchEnd() { @@ -403,13 +460,22 @@ void TouchscreenVirtualPadDevice::touchGestureEvent(const QGestureEvent* event) } } +controller::Input TouchscreenVirtualPadDevice::InputDevice::makeInput(TouchscreenVirtualPadDevice::TouchAxisChannel axis) const { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); +} + +controller::Input TouchscreenVirtualPadDevice::InputDevice::makeInput(TouchscreenVirtualPadDevice::TouchButtonChannel button) const { + return controller::Input(_deviceID, button, controller::ChannelType::BUTTON); +} + controller::Input::NamedVector TouchscreenVirtualPadDevice::InputDevice::getAvailableInputs() const { using namespace controller; QVector availableInputs{ - makePair(LX, "LX"), - makePair(LY, "LY"), - makePair(RX, "RX"), - makePair(RY, "RY") + Input::NamedPair(makeInput(TouchAxisChannel::LX), "LX"), + Input::NamedPair(makeInput(TouchAxisChannel::LY), "LY"), + Input::NamedPair(makeInput(TouchAxisChannel::RX), "RX"), + Input::NamedPair(makeInput(TouchAxisChannel::RY), "RY"), + Input::NamedPair(makeInput(TouchButtonChannel::JUMP_BUTTON_PRESS), "JUMP_BUTTON_PRESS") }; return availableInputs; } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h index 3540c6d909..212b7633ec 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h @@ -41,6 +41,18 @@ public: static const char* NAME; + int _viewTouchUpdateCount; + enum TouchAxisChannel { + LX, + LY, + RX, + RY + }; + + enum TouchButtonChannel { + JUMP_BUTTON_PRESS + }; + protected: class InputDevice : public controller::InputDevice { @@ -54,6 +66,9 @@ protected: virtual void focusOutEvent() override; friend class TouchscreenVirtualPadDevice; + + controller::Input makeInput(TouchAxisChannel axis) const; + controller::Input makeInput(TouchButtonChannel button) const; }; public: @@ -63,7 +78,8 @@ protected: enum TouchType { MOVE = 1, - VIEW + VIEW, + JUMP }; float _lastPinchScale; @@ -82,6 +98,9 @@ protected: glm::vec2 _viewCurrentTouchPoint; int _viewCurrentTouchId; + bool _jumpHasValidTouch; + int _jumpCurrentTouchId; + std::map _unusedTouches; int _touchPointCount; @@ -94,7 +113,8 @@ protected: float _fixedRadiusForCalc; int _extraBottomMargin {0}; - float _viewStickRadiusInches {0.1333f}; // agreed default + glm::vec2 _jumpButtonPosition; + float _jumpButtonRadius; void moveTouchBegin(glm::vec2 touchPoint); void moveTouchUpdate(glm::vec2 touchPoint); @@ -106,7 +126,12 @@ protected: void viewTouchEnd(); bool viewTouchBeginIsValid(glm::vec2 touchPoint); - void setupFixedCenter(VirtualPad::Manager& virtualPadManager, bool force = false); + void jumpTouchBegin(glm::vec2 touchPoint); + void jumpTouchUpdate(glm::vec2 touchPoint); + void jumpTouchEnd(); + bool jumpTouchBeginIsValid(glm::vec2 touchPoint); + + void setupControlsPositions(VirtualPad::Manager& virtualPadManager, bool force = false); void processInputDeviceForMove(VirtualPad::Manager& virtualPadManager); glm::vec2 clippedPointInCircle(float radius, glm::vec2 origin, glm::vec2 touchPoint); diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index cda22513ee..4ee893e4fc 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -358,6 +358,17 @@ namespace khronos { case InternalFormat::COMPRESSED_RG_RGTC2: // BC5 case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: // BC6 case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7 + // ETC2 / EAC + case InternalFormat::COMPRESSED_RGB8_ETC2: + case InternalFormat::COMPRESSED_SRGB8_ETC2: + case InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_RGBA8_ETC2_EAC: + case InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case InternalFormat::COMPRESSED_R11_EAC: + case InternalFormat::COMPRESSED_SIGNED_R11_EAC: + case InternalFormat::COMPRESSED_RG11_EAC: + case InternalFormat::COMPRESSED_SIGNED_RG11_EAC: return evalAlignedCompressedBlockCount<4>(value); default: @@ -370,12 +381,22 @@ namespace khronos { case InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case InternalFormat::COMPRESSED_RED_RGTC1: + case InternalFormat::COMPRESSED_RGB8_ETC2: + case InternalFormat::COMPRESSED_SRGB8_ETC2: + case InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case InternalFormat::COMPRESSED_R11_EAC: + case InternalFormat::COMPRESSED_SIGNED_R11_EAC: return 8; case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case InternalFormat::COMPRESSED_RG_RGTC2: + case InternalFormat::COMPRESSED_RGBA8_ETC2_EAC: + case InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case InternalFormat::COMPRESSED_RG11_EAC: + case InternalFormat::COMPRESSED_SIGNED_RG11_EAC: return 16; default: diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 1954f40b44..04696cea1a 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -642,8 +642,12 @@ void NetworkTexture::ktxInitialDataRequestFinished() { } if (result == ResourceRequest::Success) { + +// This is an expensive operation that we do not want in release. +#ifdef DEBUG auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString()); qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo); +#endif _ktxHeaderData = _ktxHeaderRequest->getData(); _ktxHighMipData = _ktxMipRequest->getData(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index e5e213f5c5..9a96364de2 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include "AddressManager.h" #include "NodeList.h" @@ -30,12 +29,7 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -#if USE_STABLE_GLOBAL_SERVICES -const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello"; -#else -const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello"; -#endif - +const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -45,6 +39,10 @@ bool AddressManager::isConnected() { return DependencyManager::get()->getDomainHandler().isConnected(); } +QString AddressManager::getProtocol() const { + return _domainURL.scheme(); +} + QUrl AddressManager::currentAddress(bool domainOnly) const { QUrl hifiURL = _domainURL; @@ -82,6 +80,17 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const { } } +QUrl AddressManager::currentPublicAddress(bool domainOnly) const { + // return an address that can be used by others to visit this client's current location. If + // in a serverless domain (which can't be visited) return an empty URL. + QUrl shareableAddress = currentShareableAddress(domainOnly); + if (shareableAddress.scheme() != URL_SCHEME_HIFI) { + return QUrl(); // file: urls aren't public + } + return shareableAddress; +} + + QUrl AddressManager::currentFacingShareableAddress() const { auto hifiURL = currentShareableAddress(); if (hifiURL.scheme() == URL_SCHEME_HIFI) { @@ -91,6 +100,17 @@ QUrl AddressManager::currentFacingShareableAddress() const { return hifiURL; } +QUrl AddressManager::currentFacingPublicAddress() const { + // return an address that can be used by others to visit this client's current location. If + // in a serverless domain (which can't be visited) return an empty URL. + QUrl shareableAddress = currentFacingShareableAddress(); + if (shareableAddress.scheme() != URL_SCHEME_HIFI) { + return QUrl(); // file: urls aren't public + } + return shareableAddress; +} + + void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings); @@ -293,10 +313,20 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // lookupUrl.scheme() == URL_SCHEME_HTTP || // lookupUrl.scheme() == URL_SCHEME_HTTPS || _previousLookup.clear(); - QUrl domainURL = PathUtils::expandToLocalDataAbsolutePath(lookupUrl); - setDomainInfo(domainURL, trigger); + _shareablePlaceName.clear(); + setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); - handlePath(DOMAIN_SPAWNING_POINT, LookupTrigger::Internal, false); + + QString path = DOMAIN_SPAWNING_POINT; + QUrlQuery queryArgs(lookupUrl); + const QString LOCATION_QUERY_KEY = "location"; + if (queryArgs.hasQueryItem(LOCATION_QUERY_KEY)) { + path = queryArgs.queryItemValue(LOCATION_QUERY_KEY); + } else { + path = DEFAULT_NAMED_PATH; + } + + handlePath(path, LookupTrigger::Internal, false); return true; } @@ -413,7 +443,9 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QUrl domainURL; domainURL.setScheme(URL_SCHEME_HIFI); domainURL.setHost(domainHostname); - domainURL.setPort(domainPort); + if (domainPort > 0) { + domainURL.setPort(domainPort); + } emit possibleDomainChangeRequired(domainURL, domainID); } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); @@ -584,7 +616,9 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri QUrl domainURL; domainURL.setScheme(URL_SCHEME_HIFI); domainURL.setHost(domainIPString); - domainURL.setPort(domainPort); + if (domainPort > 0) { + domainURL.setPort(domainPort); + } hostChanged = setDomainInfo(domainURL, trigger); return true; @@ -605,7 +639,9 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri QUrl domainURL; domainURL.setScheme(URL_SCHEME_HIFI); domainURL.setHost(domainHostname); - domainURL.setPort(domainPort); + if (domainPort > 0) { + domainURL.setPort(domainPort); + } hostChanged = setDomainInfo(domainURL, trigger); return true; @@ -737,7 +773,9 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 _domainURL = QUrl(); _domainURL.setScheme(URL_SCHEME_HIFI); _domainURL.setHost(host); - _domainURL.setPort(port); + if (port > 0) { + _domainURL.setPort(port); + } // any host change should clear the shareable place name _shareablePlaceName.clear(); @@ -752,14 +790,6 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 return false; } -QString AddressManager::getHost() const { - if (isPossiblePlaceName(_domainURL.host())) { - return QString(); - } - - return _domainURL.host(); -} - bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger) { const QString hostname = domainURL.host(); quint16 port = domainURL.port(); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index dc1046bf51..b6a18b117d 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -145,12 +145,14 @@ public: }; bool isConnected(); - const QString& getProtocol() { return URL_SCHEME_HIFI; }; + QString getProtocol() const; QUrl currentAddress(bool domainOnly = false) const; QUrl currentFacingAddress() const; QUrl currentShareableAddress(bool domainOnly = false) const; + QUrl currentPublicAddress(bool domainOnly = false) const; QUrl currentFacingShareableAddress() const; + QUrl currentFacingPublicAddress() const; QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; @@ -158,7 +160,7 @@ public: QString getPlaceName() const; QString getDomainID() const; - QString getHost() const; + QString getHost() const { return _domainURL.host(); } void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 7a5ecb2602..cd8064c4c0 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -9,8 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DomainHandler.h" + #include +#include + #include #include @@ -25,8 +29,6 @@ #include "UserActivityLogger.h" #include "NetworkLogging.h" -#include "DomainHandler.h" - DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), @@ -157,6 +159,11 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { if (domainURL.scheme() != URL_SCHEME_HIFI) { _sockAddr.clear(); + + // if this is a file URL we need to see if it has a ~ for us to expand + if (domainURL.scheme() == URL_SCHEME_FILE) { + domainURL = PathUtils::expandToLocalDataAbsolutePath(domainURL); + } } if (_domainURL != domainURL || _sockAddr.getPort() != domainURL.port()) { @@ -166,15 +173,15 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { QString previousHost = _domainURL.host(); _domainURL = domainURL; - if (domainURL.scheme() != URL_SCHEME_HIFI) { - setIsConnected(true); - } else if (previousHost != domainURL.host()) { + if (previousHost != domainURL.host()) { qCDebug(networking) << "Updated domain hostname to" << domainURL.host(); if (!domainURL.host().isEmpty()) { - // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname - qCDebug(networking, "Looking up DS hostname %s.", domainURL.host().toLocal8Bit().constData()); - QHostInfo::lookupHost(domainURL.host(), this, SLOT(completedHostnameLookup(const QHostInfo&))); + if (domainURL.scheme() == URL_SCHEME_HIFI) { + // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname + qCDebug(networking, "Looking up DS hostname %s.", domainURL.host().toLocal8Bit().constData()); + QHostInfo::lookupHost(domainURL.host(), this, SLOT(completedHostnameLookup(const QHostInfo&))); + } DependencyManager::get()->flagTimeForConnectionStep( LimitedNodeList::ConnectionStep::SetDomainHostname); @@ -243,6 +250,17 @@ void DomainHandler::activateICEPublicSocket() { emit completedSocketDiscovery(); } +QString DomainHandler::getViewPointFromNamedPath(QString namedPath) { + auto lookup = _namedPaths.find(namedPath); + if (lookup != _namedPaths.end()) { + return lookup->second; + } + if (namedPath == DEFAULT_NAMED_PATH) { + return DOMAIN_SPAWNING_POINT; + } + return ""; +} + void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { for (int i = 0; i < hostInfo.addresses().size(); i++) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { @@ -290,6 +308,11 @@ void DomainHandler::setIsConnected(bool isConnected) { } } +void DomainHandler::connectedToServerless(std::map namedPaths) { + _namedPaths = namedPaths; + setIsConnected(true); +} + void DomainHandler::requestDomainSettings() { qCDebug(networking) << "Requesting settings from domain server"; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index c447e9a79b..2d4712209d 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -79,6 +79,10 @@ public: void setIsConnected(bool isConnected); bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } + void connectedToServerless(std::map namedPaths); + + QString getViewPointFromNamedPath(QString namedPath); + bool hasSettings() const { return !_settingsObject.isEmpty(); } void requestDomainSettings(); const QJsonObject& getSettingsObject() const { return _settingsObject; } @@ -204,9 +208,11 @@ private: int _checkInPacketsSinceLastReply { 0 }; QTimer _apiRefreshTimer; + + std::map _namedPaths; }; const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" }; - +const QString DEFAULT_NAMED_PATH { "/" }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 88bf45a419..8570047ce2 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -290,13 +290,8 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe emit dataReceived(sendingNodeType, packet.getPayloadSize()); return true; } else { - static const QString UNSOLICITED_REPLICATED_REGEX = - "Replicated packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown upstream"; - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex(UNSOLICITED_REPLICATED_REGEX); - - qCDebug(networking) << "Replicated packet of type" << headerType - << "received from unknown upstream" << packet.getSenderSockAddr(); + HIFI_FCDEBUG(networking(), "Replicated packet of type" << headerType + << "received from unknown upstream" << packet.getSenderSockAddr()); return false; } @@ -361,12 +356,8 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe return true; } else { - static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID"; - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); - - qCDebug(networking) << "Packet of type" << headerType - << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID); + HIFI_FCDEBUG(networking(), + "Packet of type" << headerType << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID)); } } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 4b3595b279..3d8ef48be2 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -90,8 +90,8 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) // assume that we may need to send a new DS check in anytime a new keypair is generated connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); - // clear out NodeList when login is finished - connect(accountManager.data(), SIGNAL(loginComplete(const QUrl&)) , this, SLOT(reset())); + // clear out NodeList when login is finished and we know our new username + connect(accountManager.data(), SIGNAL(usernameChanged(QString)) , this, SLOT(reset())); // clear our NodeList when logout is requested connect(accountManager.data(), SIGNAL(logoutComplete()) , this, SLOT(reset())); @@ -413,7 +413,16 @@ void NodeList::sendDomainServerCheckIn() { } void NodeList::handleDSPathQuery(const QString& newPath) { - if (_domainHandler.isSocketKnown()) { + if (_domainHandler.isServerless()) { + if (_domainHandler.isConnected()) { + auto viewpoint = _domainHandler.getViewPointFromNamedPath(newPath); + if (!newPath.isEmpty()) { + DependencyManager::get()->goToViewpointForPath(viewpoint, newPath); + } + } else { + _domainHandler.setPendingPath(newPath); + } + } else if (_domainHandler.isSocketKnown()) { // if we have a DS socket we assume it will get this packet and send if off right away sendDSPathQuery(newPath); } else { @@ -427,10 +436,17 @@ void NodeList::sendPendingDSPathQuery() { QString pendingPath = _domainHandler.getPendingPath(); if (!pendingPath.isEmpty()) { - qCDebug(networking) << "Attempting to send pending query to DS for path" << pendingPath; - // this is a slot triggered if we just established a network link with a DS and want to send a path query - sendDSPathQuery(_domainHandler.getPendingPath()); + if (_domainHandler.isServerless()) { + auto viewpoint = _domainHandler.getViewPointFromNamedPath(pendingPath); + if (!pendingPath.isEmpty()) { + DependencyManager::get()->goToViewpointForPath(viewpoint, pendingPath); + } + } else { + qCDebug(networking) << "Attempting to send pending query to DS for path" << pendingPath; + // this is a slot triggered if we just established a network link with a DS and want to send a path query + sendDSPathQuery(_domainHandler.getPendingPath()); + } // clear whatever the pending path was _domainHandler.clearPendingPath(); @@ -498,7 +514,7 @@ void NodeList::processDomainServerPathResponse(QSharedPointer m QString viewpoint = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numViewpointBytes); // Hand it off to the AddressManager so it can handle it as a relative viewpoint - if (DependencyManager::get()->goToViewpointForPath(viewpoint, pathQuery)) { + if (!pathQuery.isEmpty() && DependencyManager::get()->goToViewpointForPath(viewpoint, pathQuery)) { qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery; } else { qCDebug(networking) << "Could not go to viewpoint" << viewpoint diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 03e149eb0e..e11fcf22d4 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -89,10 +89,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui } else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) { arrivalInfo._status = Unreasonable; - static const QString UNREASONABLE_SEQUENCE_REGEX { "unreasonable sequence number: \\d+ previous: \\d+" }; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(UNREASONABLE_SEQUENCE_REGEX); - - qCDebug(networking) << "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence; + HIFI_FCDEBUG(networking(), "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence); _stats._unreasonable++; @@ -154,10 +151,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui arrivalInfo._status = Unreasonable; - static const QString UNREASONABLE_SEQUENCE_REGEX { "unreasonable sequence number: \\d+ \\(possible duplicate\\)" }; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(UNREASONABLE_SEQUENCE_REGEX); - - qCDebug(networking) << "unreasonable sequence number:" << incoming << "(possible duplicate)"; + HIFI_FCDEBUG(networking(), "unreasonable sequence number:" << incoming << "(possible duplicate)"); _stats._unreasonable++; diff --git a/libraries/networking/src/udt/Packet.cpp b/libraries/networking/src/udt/Packet.cpp index f07ea63994..0456fa1223 100644 --- a/libraries/networking/src/udt/Packet.cpp +++ b/libraries/networking/src/udt/Packet.cpp @@ -107,8 +107,7 @@ Packet::Packet(std::unique_ptr data, qint64 size, const HifiSockAddr& se QString::number(getMessagePartNumber())); } - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("^Unobfuscating packet .*"); - qCDebug(networking) << qPrintable(debugString); + HIFI_FCDEBUG(networking(), debugString); #endif obfuscate(NoObfuscation); // Undo obfuscation diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 8a3592de45..8270211aea 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::ShadowControl); + return static_cast(EntityVersion::MaterialData); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 09fd31a41e..e0d3bcdb97 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -231,7 +231,8 @@ enum class EntityVersion : PacketVersion { ZoneStageRemoved, SoftEntities, MaterialEntities, - ShadowControl + ShadowControl, + MaterialData }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index b62624aab9..0df54d539d 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -479,8 +479,7 @@ bool SendQueue::maybeResendPacket() { debugString = debugString.arg(QString::number(resendPacket.getMessageNumber()), QString::number(resendPacket.getMessagePartNumber())); } - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("^Obfuscating packet .*"); - qCritical() << qPrintable(debugString); + HIFI_FDEBUG(debugString); #endif // Create copy of the packet diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 4189cb613c..4d4303698b 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -229,11 +229,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc if (bytesWritten < 0) { // when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug - static const QString WRITE_ERROR_REGEX = "Socket::writeDatagram QAbstractSocket::NetworkError - Unable to send a message"; - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex(WRITE_ERROR_REGEX); - - qCDebug(networking) << "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()); + HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()) ); } return bytesWritten; @@ -517,11 +513,7 @@ std::vector Socket::getConnectionSockAddrs() { } void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { - static const QString SOCKET_REGEX = "udt::Socket error - "; - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex(SOCKET_REGEX); - - qCDebug(networking) << "udt::Socket error - " << socketError << _udpSocket.errorString(); + HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError << _udpSocket.errorString()); } void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { diff --git a/libraries/octree/src/DirtyOctreeElementOperator.cpp b/libraries/octree/src/DirtyOctreeElementOperator.cpp index ed8d26cf72..ae01a3d608 100644 --- a/libraries/octree/src/DirtyOctreeElementOperator.cpp +++ b/libraries/octree/src/DirtyOctreeElementOperator.cpp @@ -14,6 +14,7 @@ DirtyOctreeElementOperator::DirtyOctreeElementOperator(const OctreeElementPointer& element) : _element(element) { assert(_element.get()); + _element->bumpChangedContent(); _point = _element->getAACube().calcCenter(); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index dafdfd5bf4..35db43e5e7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -45,7 +45,6 @@ #include "Octree.h" #include "OctreeConstants.h" -#include "OctreeElementBag.h" #include "OctreeLogging.h" #include "OctreeQueryNode.h" #include "OctreeUtils.h" @@ -57,7 +56,6 @@ Octree::Octree(bool shouldReaverage) : _rootElement(NULL), _isDirty(true), _shouldReaverage(shouldReaverage), - _stopImport(false), _isViewing(false), _isServer(false) { @@ -121,11 +119,7 @@ void Octree::recurseTreeWithPostOperation(const RecurseOctreeOperation& operatio void Octree::recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "Octree::recurseElementWithOperation\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - - qCDebug(octree) << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); return; } @@ -143,11 +137,7 @@ void Octree::recurseElementWithOperation(const OctreeElementPointer& element, co void Octree::recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "Octree::recurseElementWithPostOperation\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - - qCDebug(octree) << "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + HIFI_FCDEBUG(octree(), "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); return; } @@ -173,11 +163,7 @@ void Octree::recurseElementWithOperationDistanceSorted(const OctreeElementPointe const glm::vec3& point, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "Octree::recurseElementWithOperationDistanceSorted\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - - qCDebug(octree) << "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); return; } @@ -215,11 +201,7 @@ void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) { bool Octree::recurseElementWithOperator(const OctreeElementPointer& element, RecurseOctreeOperator* operatorObject, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "Octree::recurseElementWithOperator\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - - qCDebug(octree) << "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperator() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); return false; } @@ -285,11 +267,7 @@ OctreeElementPointer Octree::createMissingElement(const OctreeElementPointer& la const unsigned char* codeToReach, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "Octree::createMissingElement\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - - qCDebug(octree) << "Octree::createMissingElement() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + HIFI_FCDEBUG(octree(), "Octree::createMissingElement() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); return lastParentElement; } int indexOfNewChild = branchIndexWithDescendant(lastParentElement->getOctalCode(), codeToReach); @@ -446,16 +424,9 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, uint64_t buffe (unsigned char *)bitstreamAt, NULL); int numberOfThreeBitSectionsInStream = numberOfThreeBitSectionsInCode(bitstreamAt, bufferSizeBytes); if (numberOfThreeBitSectionsInStream > UNREASONABLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "UNEXPECTED: parsing of the octal code would make UNREASONABLY_DEEP_RECURSION... " - "numberOfThreeBitSectionsInStream: \\d+ This buffer is corrupt. Returning." - ); - - - qCDebug(octree) << "UNEXPECTED: parsing of the octal code would make UNREASONABLY_DEEP_RECURSION... " + HIFI_FCDEBUG(octree(), "UNEXPECTED: parsing of the octal code would make UNREASONABLY_DEEP_RECURSION... " "numberOfThreeBitSectionsInStream:" << numberOfThreeBitSectionsInStream << - "This buffer is corrupt. Returning."; + "This buffer is corrupt. Returning."); return; } @@ -490,131 +461,6 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, uint64_t buffe // skip bitstream to new startPoint bitstreamAt += theseBytesRead; bytesRead += theseBytesRead; - - if (args.wantImportProgress) { - emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes); - } - } -} - -void Octree::deleteOctreeElementAt(float x, float y, float z, float s) { - unsigned char* octalCode = pointToOctalCode(x,y,z,s); - deleteOctalCodeFromTree(octalCode); - delete[] octalCode; // cleanup memory -} - -class DeleteOctalCodeFromTreeArgs { -public: - bool collapseEmptyTrees; - const unsigned char* codeBuffer; - int lengthOfCode; - bool deleteLastChild; - bool pathChanged; -}; - -// Note: uses the codeColorBuffer format, but the color's are ignored, because -// this only finds and deletes the element from the tree. -void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) { - // recurse the tree while decoding the codeBuffer, once you find the element in question, recurse - // back and implement color reaveraging, and marking of lastChanged - DeleteOctalCodeFromTreeArgs args; - args.collapseEmptyTrees = collapseEmptyTrees; - args.codeBuffer = codeBuffer; - args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer); - args.deleteLastChild = false; - args.pathChanged = false; - - withWriteLock([&] { - deleteOctalCodeFromTreeRecursion(_rootElement, &args); - }); -} - -void Octree::deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData) { - DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData; - - int lengthOfElementCode = numberOfThreeBitSectionsInCode(element->getOctalCode()); - - // Since we traverse the tree in code order, we know that if our code - // matches, then we've reached our target element. - if (lengthOfElementCode == args->lengthOfCode) { - // we've reached our target, depending on how we're called we may be able to operate on it - // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep - // the logic consistent. - args->deleteLastChild = true; - return; - } - - // Ok, we know we haven't reached our target element yet, so keep looking - int childIndex = branchIndexWithDescendant(element->getOctalCode(), args->codeBuffer); - OctreeElementPointer childElement = element->getChildAtIndex(childIndex); - - // If there is no child at the target location, and the current parent element is a colored leaf, - // then it means we were asked to delete a child out of a larger leaf voxel. - // We support this by breaking up the parent voxel into smaller pieces. - if (!childElement && element->requiresSplit()) { - // we need to break up ancestors until we get to the right level - OctreeElementPointer ancestorElement = element; - while (true) { - int index = branchIndexWithDescendant(ancestorElement->getOctalCode(), args->codeBuffer); - - // we end up with all the children, even the one we want to delete - ancestorElement->splitChildren(); - - int lengthOfAncestorElement = numberOfThreeBitSectionsInCode(ancestorElement->getOctalCode()); - - // If we've reached the parent of the target, then stop breaking up children - if (lengthOfAncestorElement == (args->lengthOfCode - 1)) { - - // since we created all the children when we split, we need to delete this target one - ancestorElement->deleteChildAtIndex(index); - break; - } - ancestorElement = ancestorElement->getChildAtIndex(index); - } - _isDirty = true; - args->pathChanged = true; - - // ends recursion, unwinds up stack - return; - } - - // if we don't have a child and we reach this point, then we actually know that the parent - // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and - // we can safely return, ending the recursion and unwinding - if (!childElement) { - return; - } - - // If we got this far then we have a child for the branch we're looking for, but we're not there yet - // recurse till we get there - deleteOctalCodeFromTreeRecursion(childElement, args); - - // If the lower level determined it needs to be deleted, then we should delete now. - if (args->deleteLastChild) { - element->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this element - - // track our tree dirtiness - _isDirty = true; - - // track that path has changed - args->pathChanged = true; - - // If we're in collapseEmptyTrees mode, and this was the last child of this element, then we also want - // to delete this element. This will collapse the empty tree above us. - if (args->collapseEmptyTrees && element->getChildCount() == 0) { - // Can't delete the root this way. - if (element == _rootElement) { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } else { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } - - // If the lower level did some work, then we need to let this element know, so it can - // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc - if (args->pathChanged) { - element->handleSubtreeChanged(shared_from_this()); } } @@ -883,720 +729,6 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc return args.element; } - - -int Octree::encodeTreeBitstream(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params) { - - // How many bytes have we written so far at this level; - int bytesWritten = 0; - - // you can't call this without a valid element - if (!element) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with element=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesWritten; - } - - // you can't call this without a valid nodeData - auto octreeQueryNode = static_cast(params.nodeData); - if (!octreeQueryNode) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; - return bytesWritten; - } - - // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything && !element->isInView(params.viewFrustum)) { - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesWritten; - } - - // write the octal code - bool roomForOctalCode = false; // assume the worst - int codeLength = 1; // assume root - if (params.chopLevels) { - unsigned char* newCode = chopOctalCode(element->getOctalCode(), params.chopLevels); - roomForOctalCode = packetData->startSubTree(newCode); - - if (newCode) { - codeLength = numberOfThreeBitSectionsInCode(newCode); - delete[] newCode; - } else { - codeLength = 1; - } - } else { - roomForOctalCode = packetData->startSubTree(element->getOctalCode()); - codeLength = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(element->getOctalCode())); - } - - // If the octalcode couldn't fit, then we can return, because no nodes below us will fit... - if (!roomForOctalCode) { - bag.insert(element); - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - return bytesWritten; - } - - bytesWritten += codeLength; // keep track of byte count - - int currentEncodeLevel = 0; - - // record some stats, this is the one element that we won't record below in the recursion function, so we need to - // track it here - octreeQueryNode->stats.traversed(element); - - ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully - - int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params, - currentEncodeLevel, parentLocationThisView); - - // if childBytesWritten == 1 then something went wrong... that's not possible - assert(childBytesWritten != 1); - - // if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some - // reason couldn't be written... so reset them here... This isn't true for the non-color included case - if (suppressEmptySubtrees() && childBytesWritten == 2) { - childBytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT... - } - - // if we wrote child bytes, then return our result of all bytes written - if (childBytesWritten) { - bytesWritten += childBytesWritten; - } else { - // otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code - bytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - if (bytesWritten == 0) { - packetData->discardSubTree(); - } else { - packetData->endSubTree(); - } - - return bytesWritten; -} - -int Octree::encodeTreeBitstreamRecursion(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::intersection& parentLocationThisView) const { - - - const bool wantDebug = false; - - // The append state of this level/element. - OctreeElement::AppendState elementAppendState = OctreeElement::COMPLETED; // assume the best - - // How many bytes have we written so far at this level; - int bytesAtThisLevel = 0; - - // you can't call this without a valid element - if (!element) { - qCDebug(octree, "WARNING! encodeTreeBitstreamRecursion() called with element=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesAtThisLevel; - } - - // you can't call this without a valid nodeData - auto octreeQueryNode = static_cast(params.nodeData); - if (!octreeQueryNode) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; - return bytesAtThisLevel; - } - - - // Keep track of how deep we've encoded. - currentEncodeLevel++; - - params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached); - - // If we've reached our max Search Level, then stop searching. - if (currentEncodeLevel >= params.maxEncodeLevel) { - params.stopReason = EncodeBitstreamParams::TOO_DEEP; - return bytesAtThisLevel; - } - - ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside - if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) { - float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - - // If we're too far away for our render level, then just return - if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) { - octreeQueryNode->stats.skippedDistance(element); - params.stopReason = EncodeBitstreamParams::LOD_SKIP; - return bytesAtThisLevel; - } - - // if the parent isn't known to be INSIDE, then it must be INTERSECT, and we should double check to see - // if we are INSIDE, INTERSECT, or OUTSIDE - if (parentLocationThisView != ViewFrustum::INSIDE) { - assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE! - nodeLocationThisView = element->computeViewIntersection(params.viewFrustum); - } - - // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if - // we're out of view - if (nodeLocationThisView == ViewFrustum::OUTSIDE) { - octreeQueryNode->stats.skippedOutOfView(element); - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesAtThisLevel; - } - - // Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view - // because we don't send nodes from the previously know in view frustum. - bool wasInView = false; - - if (params.deltaView) { - ViewFrustum::intersection location = element->computeViewIntersection(params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (element->isLeaf()) { - wasInView = location != ViewFrustum::OUTSIDE; - } else { - wasInView = location == ViewFrustum::INSIDE; - } - - // If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't - // tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer - // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it - // as "was in view"... - if (wasInView) { - float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) { - // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... - wasInView = false; - } - } - } - - // If we were previously in the view, then we normally will return out of here and stop recursing. But - // if we're in deltaView mode, and this element has changed since it was last sent, then we do - // need to send it. - if (wasInView && !(params.deltaView && element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))) { - octreeQueryNode->stats.skippedWasInView(element); - params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; - return bytesAtThisLevel; - } - } - - // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, - // then we can also bail early and save bits - if (!params.forceSendScene && !params.deltaView && - !element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE)) { - - octreeQueryNode->stats.skippedNoChange(element); - - params.stopReason = EncodeBitstreamParams::NO_CHANGE; - return bytesAtThisLevel; - } - - bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! - - // At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level - // is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. - // There could be sub trees below this point, which might take many more bytes, but that's ok, because we can - // always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet - // for the remaining sub trees. - unsigned char childrenExistInTreeBits = 0; - unsigned char childrenExistInPacketBits = 0; - unsigned char childrenDataBits = 0; - - // Make our local buffer large enough to handle writing at this level in case we need to. - LevelDetails thisLevelKey = packetData->startLevel(); - int requiredBytes = sizeof(childrenDataBits) + sizeof(childrenExistInPacketBits); - if (params.includeExistsBits) { - requiredBytes += sizeof(childrenExistInTreeBits); - } - - // If this datatype allows root elements to include data, and this is the root, then ask the tree for the - // minimum bytes needed for root data and reserve those also - if (element == _rootElement && rootElementHasData()) { - requiredBytes += minimumRequiredRootDataBytes(); - } - - bool continueThisLevel = packetData->reserveBytes(requiredBytes); - - // If we can't reserve our minimum bytes then we can discard this level and return as if none of this level fits - if (!continueThisLevel) { - packetData->discardLevel(thisLevelKey); - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bag.insert(element); - return bytesAtThisLevel; - } - - int inViewCount = 0; - int inViewNotLeafCount = 0; - int inViewWithColorCount = 0; - - OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - - // if the caller wants to include childExistsBits, then include them - if (params.includeExistsBits && childElement) { - childrenExistInTreeBits += (1 << (7 - i)); - } - - sortedChildren[i] = childElement; - indexOfChildren[i] = i; - distancesToChildren[i] = 0.0f; - - // track stats - // must check childElement here, because it could be we got here with no childElement - if (childElement) { - octreeQueryNode->stats.traversed(childElement); - } - } - - // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = sortedChildren[i]; - int originalIndex = indexOfChildren[i]; - - bool childIsInView = (childElement && - (params.recurseEverything || !octreeQueryNode->getUsesFrustum() || - (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are - (nodeLocationThisView == ViewFrustum::INTERSECT && - childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view - )); - - if (!childIsInView) { - // must check childElement here, because it could be we got here because there was no childElement - if (childElement) { - octreeQueryNode->stats.skippedOutOfView(childElement); - } - } else { - // Before we consider this further, let's see if it's in our LOD scope... - float boundaryDistance = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ? 1 : - boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - - if (!(distancesToChildren[i] < boundaryDistance)) { - // don't need to check childElement here, because we can't get here with no childElement - octreeQueryNode->stats.skippedDistance(childElement); - } else { - inViewCount++; - - // track children in view as existing and not a leaf, if they're a leaf, - // we don't care about recursing deeper on them, and we don't consider their - // subtree to exist - if (!(childElement && childElement->isLeaf())) { - childrenExistInPacketBits += (1 << (7 - originalIndex)); - inViewNotLeafCount++; - } - - bool childIsOccluded = false; // assume it's not occluded - - bool shouldRender = params.recurseEverything || !octreeQueryNode->getUsesFrustum() || - childElement->calculateShouldRender(params.viewFrustum, - params.octreeElementSizeScale, params.boundaryLevelAdjust); - - // track some stats - // don't need to check childElement here, because we can't get here with no childElement - if (!shouldRender && childElement->isLeaf()) { - octreeQueryNode->stats.skippedDistance(childElement); - } - // don't need to check childElement here, because we can't get here with no childElement - if (childIsOccluded) { - octreeQueryNode->stats.skippedOccluded(childElement); - } - - // track children with actual color, only if the child wasn't previously in view! - if (shouldRender && !childIsOccluded) { - bool childWasInView = false; - - if (childElement && params.deltaView) { - ViewFrustum::intersection location = childElement->computeViewIntersection(params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (childElement->isLeaf()) { - childWasInView = location != ViewFrustum::OUTSIDE; - } else { - childWasInView = location == ViewFrustum::INSIDE; - } - } - - // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items. - // Or if we were previously in the view, but this element has changed since it was last sent, then we do - // need to send it. - if (!childWasInView || - (params.deltaView && - childElement->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))){ - - childrenDataBits += (1 << (7 - originalIndex)); - inViewWithColorCount++; - } else { - // otherwise just track stats of the items we discarded - // don't need to check childElement here, because we can't get here with no childElement - if (childWasInView) { - octreeQueryNode->stats.skippedWasInView(childElement); - } else { - octreeQueryNode->stats.skippedNoChange(childElement); - } - } - } - } - } - } - - // NOTE: the childrenDataBits indicates that there is an array of child element data included in this packet. - // We will write this bit mask but we may come back later and update the bits that are actually included - packetData->releaseReservedBytes(sizeof(childrenDataBits)); - continueThisLevel = packetData->appendBitMask(childrenDataBits); - - int childDataBitsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenDataBits)); - unsigned char actualChildrenDataBits = 0; - - assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail - bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count - - octreeQueryNode->stats.colorBitsWritten(); // really data bits not just color bits - - // NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data - element->initializeExtraEncodeData(params); - - // write the child element data... - // NOTE: the format of the bitstream is generally this: - // [octalcode] - // [bitmask for existence of child data] - // N x [child data] - // [bitmask for existence of child elements in tree] - // [bitmask for existence of child elements in buffer] - // N x [ ... tree for children ...] - // - // This section of the code, is writing the "N x [child data]" portion of this bitstream - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenDataBits, i)) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - - // the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already - // processed and sent the data bits for. Let our tree subclass determine if it really wants to send the - // data for this child at this point - if (childElement && element->shouldIncludeChildData(i, params)) { - - int bytesBeforeChild = packetData->getUncompressedSize(); - - // a childElement may "partially" write it's data. for example, the model server where the entire - // contents of the element may be larger than can fit in a single MTU/packetData. In this case, - // we want to allow the appendElementData() to respond that it produced partial data, which should be - // written, but that the childElement needs to be reprocessed in an additional pass or passes - // to be completed. - LevelDetails childDataLevelKey = packetData->startLevel(); - - OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params); - - // allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state - element->updateEncodedData(i, childAppendState, params); - - // Continue this level so long as some part of this child element was appended. - bool childFit = (childAppendState != OctreeElement::NONE); - - // some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit - // the data type wants to bail on this element level completely - if (!childFit && mustIncludeAllChildData()) { - continueThisLevel = false; - break; - } - - // If the child was partially or fully appended, then mark the actualChildrenDataBits as including - // this child data - if (childFit) { - actualChildrenDataBits += (1 << (7 - i)); - continueThisLevel = packetData->endLevel(childDataLevelKey); - } else { - packetData->discardLevel(childDataLevelKey); - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - // If this child was partially appended, then consider this element to be partially appended - if (childAppendState == OctreeElement::PARTIAL) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - int bytesAfterChild = packetData->getUncompressedSize(); - - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - - // don't need to check childElement here, because we can't get here with no childElement - if (childAppendState != OctreeElement::NONE) { - octreeQueryNode->stats.colorSent(childElement); - } - } - } - } - - if (!mustIncludeAllChildData() && !continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: reached end of child element data loop with continueThisLevel=FALSE"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - - if (continueThisLevel && actualChildrenDataBits != childrenDataBits) { - // repair the child data mask - continueThisLevel = packetData->updatePriorBitMask(childDataBitsPlaceHolder, actualChildrenDataBits); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childDataBitsPlaceHolder"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // if the caller wants to include childExistsBits, then include them even if not in view, put them before the - // childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits - if (continueThisLevel && params.includeExistsBits) { - packetData->releaseReservedBytes(sizeof(childrenExistInTreeBits)); - continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count - - octreeQueryNode->stats.existsBitsWritten(); - } else { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // write the child exist bits - if (continueThisLevel) { - packetData->releaseReservedBytes(sizeof(childrenExistInPacketBits)); - continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count - - octreeQueryNode->stats.existsInPacketBitsWritten(); - } else { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // We only need to keep digging, if there is at least one child that is inView, and not a leaf. - keepDiggingDeeper = (inViewNotLeafCount > 0); - - // - // NOTE: the format of the bitstream is generally this: - // [octalcode] - // [bitmask for existence of child data] - // N x [child data] - // [bitmask for existence of child elements in tree] - // [bitmask for existence of child elements in buffer] - // N x [ ... tree for children ...] - // - // This section of the code, is writing the "N x [ ... tree for children ...]" portion of this bitstream - // - if (continueThisLevel && keepDiggingDeeper) { - - // at this point, we need to iterate the children who are in view, even if not colored - // and we need to determine if there's a deeper tree below them that we care about. - // - // Since this recursive function assumes we're already writing, we know we've already written our - // childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if - // we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is - // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they - // write something, we keep them in the bits, if they don't, we take them out. - // - // we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was! - int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits)); - - // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the - // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, - // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make - // a single recursive pass in distance sorted order, but retain standard order in our encoded packet - - // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int indexByDistance = 0; indexByDistance < NUMBER_OF_CHILDREN; indexByDistance++) { - OctreeElementPointer childElement = sortedChildren[indexByDistance]; - int originalIndex = indexOfChildren[indexByDistance]; - - if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - - int thisLevel = currentEncodeLevel; - - int childTreeBytesOut = 0; - - // NOTE: some octree styles (like models and particles) will store content in parent elements, and child - // elements. In this case, if we stop recursion when we include any data (the colorbits should really be - // called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep - // recursing, by returning TRUE in recurseChildrenWithData(). - - if (params.recurseEverything || !octreeQueryNode->getUsesFrustum() - || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) { - - // Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this - // will be true. But if the tree has already been encoded, we will skip this. - if (element->shouldRecurseChildTree(originalIndex, params)) { - childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params, - thisLevel, nodeLocationThisView); - } else { - childTreeBytesOut = 0; - } - } - - // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, - // basically, the children below don't contain any info. - - // if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color - // byte and the child exist byte. - // - assert(childTreeBytesOut != 1); - - // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... - // if it had colors it would write 1 byte for the color mask, - // and at least a color's worth of bytes for the element of colors. - // if it had child trees (with something in them) then it would have the 1 byte for child mask - // and some number of bytes of lower children... - // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! - // - // we can make this act like no bytes out, by just resetting the bytes out in this case - if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) { - childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees - - } - - bytesAtThisLevel += childTreeBytesOut; - - // If we had previously started writing, and if the child DIDN'T write any bytes, - // then we want to remove their bit from the childExistsPlaceHolder bitmask - if (childTreeBytesOut == 0) { - - // remove this child's bit... - childrenExistInPacketBits -= (1 << (7 - originalIndex)); - - // repair the child exists mask - continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childExistsPlaceHolder"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - - // If this is the last of the child exists bits, then we're actually be rolling out the entire tree - if (childrenExistInPacketBits == 0) { - octreeQueryNode->stats.childBitsRemoved(params.includeExistsBits); - } - - if (!continueThisLevel) { - if (wantDebug) { - qCDebug(octree) << " WARNING line:" << __LINE__; - qCDebug(octree) << " breaking the child recursion loop with continueThisLevel=false!!!"; - qCDebug(octree) << " AFTER attempting to updatePriorBitMask() for empty sub tree...."; - qCDebug(octree) << " IS THIS ACCEPTABLE!!!!"; - } - break; // can't continue... - } - - // Note: no need to move the pointer, cause we already stored this - } // end if (childTreeBytesOut == 0) - } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) - } // end for - } // end keepDiggingDeeper - - // If we made it this far, then we've written all of our child data... if this element is the root - // element, then we also allow the root element to write out it's data... - if (continueThisLevel && element == _rootElement && rootElementHasData()) { - int bytesBeforeChild = packetData->getUncompressedSize(); - - // release the bytes we reserved... - packetData->releaseReservedBytes(minimumRequiredRootDataBytes()); - - LevelDetails rootDataLevelKey = packetData->startLevel(); - OctreeElement::AppendState rootAppendState = element->appendElementData(packetData, params); - - bool partOfRootFit = (rootAppendState != OctreeElement::NONE); - bool allOfRootFit = (rootAppendState == OctreeElement::COMPLETED); - - if (partOfRootFit) { - continueThisLevel = packetData->endLevel(rootDataLevelKey); - if (!continueThisLevel) { - qCDebug(octree) << " UNEXPECTED ROOT ELEMENT -- could not packetData->endLevel(rootDataLevelKey) -- line:" << __LINE__; - } - } else { - packetData->discardLevel(rootDataLevelKey); - } - - if (!allOfRootFit) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - // do we really ever NOT want to continue this level??? - //continueThisLevel = (rootAppendState == OctreeElement::COMPLETED); - - int bytesAfterChild = packetData->getUncompressedSize(); - - if (continueThisLevel) { - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - - octreeQueryNode->stats.colorSent(element); - } - - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in packing ROOT data"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // if we were unable to fit this level in our packet, then rewind and add it to the element bag for - // sending later... - if (continueThisLevel) { - continueThisLevel = packetData->endLevel(thisLevelKey); - } else { - packetData->discardLevel(thisLevelKey); - - if (!mustIncludeAllChildData()) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // This happens if the element could not be written at all. In the case of Octree's that support partial - // element data, continueThisLevel will be true. So this only happens if the full element needs to be - // added back to the element bag. - if (!continueThisLevel) { - if (!mustIncludeAllChildData()) { - qCDebug(octree) << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element."; - qCDebug(octree) << " If the datatype requires all child data, then this might happen. Otherwise" ; - qCDebug(octree) << " this is an unexpected case and we should research a potential logic error." ; - } - - bag.insert(element); - - // don't need to check element here, because we can't get here with no element - octreeQueryNode->stats.didntFit(element); - - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bytesAtThisLevel = 0; // didn't fit - } else { - - // assuming we made it here with continueThisLevel == true, we STILL might want - // to add our element back to the bag for additional encoding, specifically if - // the appendState is PARTIAL, in this case, we re-add our element to the bag - // and assume that the appendElementData() has stored any required state data - // in the params extraEncodeData - if (elementAppendState == OctreeElement::PARTIAL) { - bag.insert(element); - } - } - - // If our element is completed let the element know so it can do any cleanup it of extra wants - if (elementAppendState == OctreeElement::COMPLETED) { - element->elementEncodeComplete(params); - } - - return bytesAtThisLevel; -} - bool Octree::readFromFile(const char* fileName) { QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); @@ -1615,14 +747,10 @@ bool Octree::readFromFile(const char* fileName) { QFileInfo fileInfo(qFileName); uint64_t fileLength = fileInfo.size(); - emit importSize(1.0f, 1.0f, 1.0f); - emit importProgress(0); - qCDebug(octree) << "Loading file" << qFileName << "..."; bool success = readFromStream(fileLength, fileInputStream); - emit importProgress(100); file.close(); return success; @@ -1863,7 +991,3 @@ bool Octree::countOctreeElementsOperation(const OctreeElementPointer& element, v (*(uint64_t*)extraData)++; return true; // keep going } - -void Octree::cancelImport() { - _stopImport = true; -} diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index c4c4508138..a2ad834e18 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -49,126 +49,62 @@ public: // Callback function, for recuseTreeWithOperation using RecurseOctreeOperation = std::function; -typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; typedef QHash CubeList; const bool NO_EXISTS_BITS = false; const bool WANT_EXISTS_BITS = true; -const bool COLLAPSE_EMPTY_TREE = true; -const bool DONT_COLLAPSE = false; -const int DONT_CHOP = 0; const int NO_BOUNDARY_ADJUST = 0; const int LOW_RES_MOVING_ADJUST = 1; -#define IGNORE_COVERAGE_MAP NULL - class EncodeBitstreamParams { public: ViewFrustum viewFrustum; - ViewFrustum lastViewFrustum; - int maxEncodeLevel; - int maxLevelReached; bool includeExistsBits; - int chopLevels; - bool deltaView; - bool recurseEverything { false }; - int boundaryLevelAdjust; - float octreeElementSizeScale; - bool forceSendScene; NodeData* nodeData; // output hints from the encode process typedef enum { UNKNOWN, DIDNT_FIT, - NULL_NODE, - NULL_NODE_DATA, - TOO_DEEP, - LOD_SKIP, - OUT_OF_VIEW, - WAS_IN_VIEW, - NO_CHANGE, - OCCLUDED, FINISHED } reason; reason stopReason; - EncodeBitstreamParams( - int maxEncodeLevel = INT_MAX, - bool includeExistsBits = WANT_EXISTS_BITS, - int chopLevels = 0, - bool useDeltaView = false, - int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, - float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, - bool forceSendScene = true, - NodeData* nodeData = nullptr) : - maxEncodeLevel(maxEncodeLevel), - maxLevelReached(0), + EncodeBitstreamParams(bool includeExistsBits = WANT_EXISTS_BITS, + NodeData* nodeData = nullptr) : includeExistsBits(includeExistsBits), - chopLevels(chopLevels), - deltaView(useDeltaView), - boundaryLevelAdjust(boundaryLevelAdjust), - octreeElementSizeScale(octreeElementSizeScale), - forceSendScene(forceSendScene), nodeData(nodeData), stopReason(UNKNOWN) { - lastViewFrustum.invalidate(); } void displayStopReason() { printf("StopReason: "); switch (stopReason) { - default: case UNKNOWN: qDebug("UNKNOWN"); break; - case DIDNT_FIT: qDebug("DIDNT_FIT"); break; - case NULL_NODE: qDebug("NULL_NODE"); break; - case TOO_DEEP: qDebug("TOO_DEEP"); break; - case LOD_SKIP: qDebug("LOD_SKIP"); break; - case OUT_OF_VIEW: qDebug("OUT_OF_VIEW"); break; - case WAS_IN_VIEW: qDebug("WAS_IN_VIEW"); break; - case NO_CHANGE: qDebug("NO_CHANGE"); break; - case OCCLUDED: qDebug("OCCLUDED"); break; + case FINISHED: qDebug("FINISHED"); break; } } QString getStopReason() { switch (stopReason) { - default: case UNKNOWN: return QString("UNKNOWN"); break; - case DIDNT_FIT: return QString("DIDNT_FIT"); break; - case NULL_NODE: return QString("NULL_NODE"); break; - case TOO_DEEP: return QString("TOO_DEEP"); break; - case LOD_SKIP: return QString("LOD_SKIP"); break; - case OUT_OF_VIEW: return QString("OUT_OF_VIEW"); break; - case WAS_IN_VIEW: return QString("WAS_IN_VIEW"); break; - case NO_CHANGE: return QString("NO_CHANGE"); break; - case OCCLUDED: return QString("OCCLUDED"); break; + case FINISHED: return QString("FINISHED"); break; } } std::function trackSend { [](const QUuid&, quint64){} }; }; -class ReadElementBufferToTreeArgs { -public: - const unsigned char* buffer; - int length; - bool destructive; - bool pathChanged; -}; - class ReadBitstreamToTreeParams { public: bool includeExistsBits; OctreeElementPointer destinationElement; QUuid sourceUUID; SharedNodePointer sourceNode; - bool wantImportProgress; - PacketVersion bitstreamVersion; int elementsPerPacket = 0; int entitiesPerPacket = 0; @@ -176,15 +112,11 @@ public: bool includeExistsBits = WANT_EXISTS_BITS, OctreeElementPointer destinationElement = NULL, QUuid sourceUUID = QUuid(), - SharedNodePointer sourceNode = SharedNodePointer(), - bool wantImportProgress = false, - PacketVersion bitstreamVersion = 0) : + SharedNodePointer sourceNode = SharedNodePointer()) : includeExistsBits(includeExistsBits), destinationElement(destinationElement), sourceUUID(sourceUUID), - sourceNode(sourceNode), - wantImportProgress(wantImportProgress), - bitstreamVersion(bitstreamVersion) + sourceNode(sourceNode) {} }; @@ -199,7 +131,6 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing - virtual bool getWantSVOfileVersions() const { return false; } virtual PacketType expectedDataPacketType() const { return PacketType::Unknown; } virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); } virtual bool handlesEditPacketType(PacketType packetType) const { return false; } @@ -209,12 +140,8 @@ public: virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } - virtual bool recurseChildrenWithData() const { return true; } virtual bool rootElementHasData() const { return false; } - virtual int minimumRequiredRootDataBytes() const { return 0; } - virtual bool suppressEmptySubtrees() const { return true; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const { } - virtual bool mustIncludeAllChildData() const { return true; } virtual void update() { } // nothing to do by default @@ -223,11 +150,8 @@ public: virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); - void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer()); - void deleteOctreeElementAt(float x, float y, float z, float s); - /// Find the voxel at position x,y,z,s /// \return pointer to the OctreeElement or NULL if none at x,y,z,s. OctreeElementPointer getOctreeElementAt(float x, float y, float z, float s) const; @@ -250,8 +174,6 @@ public: void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject); - int encodeTreeBitstream(const OctreeElementPointer& element, OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params) ; bool isDirty() const { return _isDirty; } void clearDirtyBit() { _isDirty = false; } @@ -344,22 +266,10 @@ public: void incrementPersistDataVersion() { _persistDataVersion++; } -signals: - void importSize(float x, float y, float z); - void importProgress(int progress); - -public slots: - void cancelImport(); - protected: void deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData); - int encodeTreeBitstreamRecursion(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::intersection& parentLocationThisView) const; - static bool countOctreeElementsOperation(const OctreeElementPointer& element, void* extraData); OctreeElementPointer nodeForOctalCode(const OctreeElementPointer& ancestorElement, const unsigned char* needleCode, OctreeElementPointer* parentOfFoundElement) const; @@ -374,7 +284,6 @@ protected: bool _isDirty; bool _shouldReaverage; - bool _stopImport; bool _isViewing; bool _isServer; diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 989951b661..a666ba0426 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -401,11 +401,7 @@ OctreeElementPointer OctreeElement::addChildAtIndex(int childIndex) { bool OctreeElement::safeDeepDeleteChildAtIndex(int childIndex, int recursionCount) { bool deleteApproved = false; if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "OctreeElement::safeDeepDeleteChildAtIndex\\(\\) reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - - qCDebug(octree) << "OctreeElement::safeDeepDeleteChildAtIndex() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + HIFI_FCDEBUG(octree(), "OctreeElement::safeDeepDeleteChildAtIndex() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); return deleteApproved; } OctreeElementPointer childToDelete = getChildAtIndex(childIndex); @@ -461,33 +457,6 @@ ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrust return viewFrustum.calculateCubeKeyholeIntersection(_cube); } -// There are two types of nodes for which we want to "render" -// 1) Leaves that are in the LOD -// 2) Non-leaves are more complicated though... usually you don't want to render them, but if their children -// wouldn't be rendered, then you do want to render them. But sometimes they have some children that ARE -// in the LOD, and others that are not. In this case we want to render the parent, and none of the children. -// -// Since, if we know the camera position and orientation, we can know which of the corners is the "furthest" -// corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of. -// By doing this, we don't need to test each child voxel's position vs the LOD boundary -bool OctreeElement::calculateShouldRender(const ViewFrustum& viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { - bool shouldRender = false; - - if (hasContent()) { - float furthestDistance = furthestDistanceToCamera(viewFrustum); - float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize); - bool inChildBoundary = (furthestDistance <= childBoundary); - if (hasDetailedContent() && inChildBoundary) { - shouldRender = true; - } else { - float boundary = childBoundary * 2.0f; // the boundary is always twice the distance of the child boundary - bool inBoundary = (furthestDistance <= boundary); - shouldRender = inBoundary && !inChildBoundary; - } - } - return shouldRender; -} - // Calculates the distance to the furthest point of the voxel to the camera // does as much math as possible in voxel scale and then scales up to TREE_SCALE at end float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const { diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 9ab5d9429d..b7857c3e6c 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -85,16 +85,6 @@ public: typedef enum { COMPLETED, PARTIAL, NONE } AppendState; virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const { } - virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { } - virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; } - virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; } - - virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { } - virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { } - - /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const - { return COMPLETED; } /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. @@ -139,9 +129,6 @@ public: float distanceToCamera(const ViewFrustum& viewFrustum) const; float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const; - bool calculateShouldRender(const ViewFrustum& viewFrustum, - float voxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; - // points are assumed to be in Voxel Coordinates (not TREE_SCALE'd) float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this. float distanceToPoint(const glm::vec3& point) const; @@ -219,6 +206,9 @@ public: int getMyChildContaining(const AABox& box) const; int getMyChildContainingPoint(const glm::vec3& point) const; + void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); } + uint64_t getLastChangedContent() const { return _lastChangedContent; } + protected: void deleteAllChildren(); @@ -235,6 +225,7 @@ protected: } _octalCode; quint64 _lastChanged; /// Client and server, timestamp this node was last changed, 8 bytes + uint64_t _lastChangedContent { 0 }; /// Client and server, pointers to child nodes, various encodings #ifdef SIMPLE_CHILD_ARRAY diff --git a/libraries/octree/src/OctreeElementBag.cpp b/libraries/octree/src/OctreeElementBag.cpp deleted file mode 100644 index afd2d5cdc3..0000000000 --- a/libraries/octree/src/OctreeElementBag.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// OctreeElementBag.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 4/25/2013. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OctreeElementBag.h" -#include - -void OctreeElementBag::deleteAll() { - _bagElements = Bag(); -} - -/// does the bag contain elements? -/// if all of the contained elements are expired, they will not report as empty, and -/// a single last item will be returned by extract as a null pointer -bool OctreeElementBag::isEmpty() { - return _bagElements.empty(); -} - -void OctreeElementBag::insert(const OctreeElementPointer& element) { - _bagElements[element.get()] = element; -} - -OctreeElementPointer OctreeElementBag::extract() { - OctreeElementPointer result; - - // Find the first element still alive - Bag::iterator it = _bagElements.begin(); - while (it != _bagElements.end() && !result) { - result = it->second.lock(); - it = _bagElements.erase(it); - } - return result; -} diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h index 34c49f1e60..a79648f596 100644 --- a/libraries/octree/src/OctreeElementBag.h +++ b/libraries/octree/src/OctreeElementBag.h @@ -16,30 +16,8 @@ #ifndef hifi_OctreeElementBag_h #define hifi_OctreeElementBag_h -#include - #include "OctreeElement.h" -class OctreeElementBag { - using Bag = std::unordered_map; - -public: - void insert(const OctreeElementPointer& element); // put a element into the bag - - OctreeElementPointer extract(); /// pull a element out of the bag (could come in any order) and if all of the - /// elements have expired, a single null pointer will be returned - - bool isEmpty(); /// does the bag contain elements, - /// if all of the contained elements are expired, they will not report as empty, and - /// a single last item will be returned by extract as a null pointer - - void deleteAll(); - size_t size() const { return _bagElements.size(); } - -private: - Bag _bagElements; -}; - class OctreeElementExtraEncodeDataBase { public: OctreeElementExtraEncodeDataBase() {} diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 7c5b7eb45c..e6c28f75e8 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -192,17 +192,12 @@ bool OctreePersistThread::process() { QString lockFileName = _filename + ".lock"; std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate); if (lockFile.is_open()) { - qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName - << "-- Attempting to restore from previous backup file."; - - // This is where we should attempt to find the most recent backup and restore from - // that file as our persist file. - restoreFromMostRecentBackup(); + qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName; lockFile.close(); - qCDebug(octree) << "Loading Octree... lock file closed:" << lockFileName; + qCDebug(octree) << "Removing lock file:" << lockFileName; remove(qPrintable(lockFileName)); - qCDebug(octree) << "Loading Octree... lock file removed:" << lockFileName; + qCDebug(octree) << "Lock file removed:" << lockFileName; } persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 14c37d5116..43019c7acc 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -117,7 +117,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, - sourceUUID, sourceNode, false, message.getVersion()); + sourceUUID, sourceNode); quint64 startUncompress, startLock = usecTimestampNow(); quint64 startReadBitsteam, endReadBitsteam; // FIXME STUTTER - there may be an opportunity to bump this lock outside of the diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index f0c9027493..16542b697e 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -144,11 +144,6 @@ void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const { viewOut = _currentViewFrustum; } -void OctreeQueryNode::copyLastKnownViewFrustum(ViewFrustum& viewOut) const { - QMutexLocker viewLocker(&_viewMutex); - viewOut = _lastKnownViewFrustum; -} - bool OctreeQueryNode::updateCurrentViewFrustum() { // if shutting down, return immediately if (_isShuttingDown) { @@ -229,70 +224,6 @@ void OctreeQueryNode::setViewSent(bool viewSent) { } } -void OctreeQueryNode::updateLastKnownViewFrustum() { - // if shutting down, return immediately - if (_isShuttingDown) { - return; - } - - { - QMutexLocker viewLocker(&_viewMutex); - bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); - - if (frustumChanges) { - // save our currentViewFrustum into our lastKnownViewFrustum - _lastKnownViewFrustum = _currentViewFrustum; - } - } - - // save that we know the view has been sent. - setLastTimeBagEmpty(); -} - - -bool OctreeQueryNode::moveShouldDump() const { - // if shutting down, return immediately - if (_isShuttingDown) { - return false; - } - - QMutexLocker viewLocker(&_viewMutex); - glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition(); - glm::vec3 newPosition = _currentViewFrustum.getPosition(); - - // theoretically we could make this slightly larger but relative to avatar scale. - const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f; - return glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP; -} - -void OctreeQueryNode::dumpOutOfView() { - // if shutting down, return immediately - if (_isShuttingDown) { - return; - } - - int stillInView = 0; - int outOfView = 0; - OctreeElementBag tempBag; - ViewFrustum viewCopy; - copyCurrentViewFrustum(viewCopy); - while (OctreeElementPointer elementToCheck = elementBag.extract()) { - if (elementToCheck->isInView(viewCopy)) { - tempBag.insert(elementToCheck); - stillInView++; - } else { - outOfView++; - } - } - if (stillInView > 0) { - while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) { - if (elementToKeepInBag->isInView(viewCopy)) { - elementBag.insert(elementToKeepInBag); - } - } - } -} - void OctreeQueryNode::packetSent(const NLPacket& packet) { _sentPacketHistory.packetSent(_sequenceNumber, packet); _sequenceNumber++; diff --git a/libraries/octree/src/OctreeQueryNode.h b/libraries/octree/src/OctreeQueryNode.h index fd89a89949..640a7c7ddc 100644 --- a/libraries/octree/src/OctreeQueryNode.h +++ b/libraries/octree/src/OctreeQueryNode.h @@ -46,23 +46,14 @@ public: bool shouldSuppressDuplicatePacket(); unsigned int getAvailable() const { return _octreePacket->bytesAvailableForWrite(); } - int getMaxSearchLevel() const { return _maxSearchLevel; } - void resetMaxSearchLevel() { _maxSearchLevel = 1; } - void incrementMaxSearchLevel() { _maxSearchLevel++; } - int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; } - void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } - - OctreeElementBag elementBag; OctreeElementExtraEncodeData extraEncodeData; void copyCurrentViewFrustum(ViewFrustum& viewOut) const; - void copyLastKnownViewFrustum(ViewFrustum& viewOut) const; // These are not classic setters because they are calculating and maintaining state // which is set asynchronously through the network receive bool updateCurrentViewFrustum(); - void updateLastKnownViewFrustum(); bool getViewSent() const { return _viewSent; } void setViewSent(bool viewSent); @@ -70,24 +61,13 @@ public: bool getViewFrustumChanging() const { return _viewFrustumChanging; } bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; } - bool moveShouldDump() const; - - quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; } - void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; } - bool hasLodChanged() const { return _lodChanged; } OctreeSceneStats stats; - void dumpOutOfView(); - - quint64 getLastRootTimestamp() const { return _lastRootTimestamp; } - void setLastRootTimestamp(quint64 timestamp) { _lastRootTimestamp = timestamp; } unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; } int getDuplicatePacketCount() const { return _duplicatePacketCount; } - void sceneStart(quint64 sceneSendStartTime) { _sceneSendStartTime = sceneSendStartTime; } - void nodeKilled(); bool isShuttingDown() const { return _isShuttingDown; } @@ -118,18 +98,11 @@ private: int _duplicatePacketCount { 0 }; quint64 _firstSuppressedPacket { usecTimestampNow() }; - int _maxSearchLevel { 1 }; - int _maxLevelReachedInLastSearch { 1 }; - mutable QMutex _viewMutex { QMutex::Recursive }; ViewFrustum _currentViewFrustum; - ViewFrustum _lastKnownViewFrustum; - quint64 _lastTimeBagEmpty { 0 }; bool _viewFrustumChanging { false }; bool _viewFrustumJustStoppedChanging { true }; - OctreeSendThread* _octreeSendThread { nullptr }; - // watch for LOD changes int _lastClientBoundaryLevelAdjust { 0 }; float _lastClientOctreeSizeScale { DEFAULT_OCTREE_SIZE_SCALE }; @@ -138,16 +111,12 @@ private: OCTREE_PACKET_SEQUENCE _sequenceNumber { 0 }; - quint64 _lastRootTimestamp { 0 }; - PacketType _myPacketType { PacketType::Unknown }; bool _isShuttingDown { false }; SentPacketHistory _sentPacketHistory; QQueue _nackedSequenceNumbers; - quint64 _sceneSendStartTime = 0; - std::array _lastOctreePayload; QJsonObject _lastCheckJSONParameters; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 054603440d..b2efdfd595 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -284,10 +284,6 @@ void OctreeSceneStats::didntFit(const OctreeElementPointer& element) { } } -void OctreeSceneStats::colorBitsWritten() { - _colorBitsWritten++; -} - void OctreeSceneStats::existsBitsWritten() { _existsBitsWritten++; } @@ -636,12 +632,8 @@ void OctreeSceneStats::trackIncomingOctreePacket(ReceivedMessage& message, bool const qint64 MAX_RESONABLE_FLIGHT_TIME = 200 * USECS_PER_SECOND; // 200 seconds is more than enough time for a packet to arrive const qint64 MIN_RESONABLE_FLIGHT_TIME = -1 * (qint64)USECS_PER_SECOND; // more than 1 second of "reverse flight time" would be unreasonable if (flightTime > MAX_RESONABLE_FLIGHT_TIME || flightTime < MIN_RESONABLE_FLIGHT_TIME) { - static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex( - "ignoring unreasonable packet... flightTime: -?\\d+ nodeClockSkewUsec: -?\\d+ usecs"); - - qCDebug(octree) << "ignoring unreasonable packet... flightTime:" << flightTime - << "nodeClockSkewUsec:" << nodeClockSkewUsec << "usecs";; + HIFI_FCDEBUG(octree(), "ignoring unreasonable packet... flightTime:" << flightTime + << "nodeClockSkewUsec:" << nodeClockSkewUsec << "usecs"); return; // ignore any packets that are unreasonable } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 78b4dfd26f..e1228609a6 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -79,9 +79,6 @@ public: /// Track that a element was due to be sent, but didn't fit in the packet and was moved to next packet void didntFit(const OctreeElementPointer& element); - /// Track that the color bitmask was was sent as part of computation of a scene - void colorBitsWritten(); - /// Track that the exists in tree bitmask was was sent as part of computation of a scene void existsBitsWritten(); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 420da5a1e0..c2bacd4949 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,12 +26,7 @@ #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS #include "EntityTree.h" -#endif -const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; -const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; - -#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -46,11 +41,13 @@ bool entityTreeIsLocked() { } #endif +const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; +const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; + EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(nullptr), - _entityPtr(entity), - _entity(entity.get()), + _entity(entity), _serverPosition(0.0f), _serverRotation(), _serverVelocity(0.0f), @@ -60,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverActionData(QByteArray()), _lastVelocity(0.0f), _measuredAcceleration(0.0f), - _nextOwnershipBid(0), + _nextBidExpiry(0), _measuredDeltaTime(0.0f), _lastMeasureStep(0), _lastStep(0), @@ -68,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _accelerationNearlyGravityCount(0), _numInactiveUpdates(1) { + // Why is _numInactiveUpdates initialied to 1? + // Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed, + // which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just + // went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates + // to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case. + _type = MOTIONSTATE_TYPE_ENTITY; assert(_entity); assert(entityTreeIsLocked()); @@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - _outgoingPriority = _entity->getPendingOwnershipPriority(); -} - -EntityMotionState::~EntityMotionState() { - assert(_entity); - _entity = nullptr; -} - -void EntityMotionState::updateServerPhysicsVariables() { - assert(entityTreeIsLocked()); - if (isLocallyOwned()) { - // don't slam these values if we are the simulation owner - return; + _bidPriority = _entity->getPendingOwnershipPriority(); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // client-only entities are always thus, so we cache this fact in _ownershipState + _ownershipState = EntityMotionState::OwnershipState::Unownable; } Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); - _serverVariablesSet = true; _serverPosition = localTransform.getTranslation(); _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getDynamicData(); + +} + +EntityMotionState::~EntityMotionState() { + if (_entity) { + assert(_entity->getPhysicsInfo() == this); + _entity->setPhysicsInfo(nullptr); + _entity.reset(); + } +} + +void EntityMotionState::updateServerPhysicsVariables() { + if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { + // only slam these values if we are NOT the simulation owner + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); + } } void EntityMotionState::handleDeactivation() { - if (_serverVariablesSet) { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero - } + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero } // virtual void EntityMotionState::handleEasyChanges(uint32_t& flags) { - assert(_entity); assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags); if (flags & Simulation::DIRTY_SIMULATOR_ID) { if (_entity->getSimulatorID().isNull()) { - // simulation ownership has been removed by an external simulator + // simulation ownership has been removed if (glm::length2(_entity->getWorldVelocity()) == 0.0f) { - // this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority + // this object is coming to rest --> clear the ACTIVATION flag and _bidPriority flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; _body->setActivationState(WANTS_DEACTIVATION); - _outgoingPriority = 0; + _bidPriority = 0; const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet _body->setDeactivationTime(ACTIVATION_EXPIRY); } else { // disowned object is still moving --> start timer for ownership bid // TODO? put a delay in here proportional to distance from object? - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; _numInactiveUpdates = 0; } else if (isLocallyOwned()) { // we just inherited ownership, make sure our desired priority matches what we have - upgradeOutgoingPriority(_entity->getSimulationPriority()); + upgradeBidPriority(_entity->getSimulationPriority()); } else { - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + // the entity is owned by someone else, so we clear _bidPriority here + // but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation + // in which case we may try to bid again + _bidPriority = 0; + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _numInactiveUpdates = 0; } } @@ -155,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - upgradeOutgoingPriority(newPriority); + upgradeBidPriority(newPriority); // reset bid expiry so that we bid ASAP - _nextOwnershipBid = 0; + _nextBidExpiry = 0; } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { if (_body->isKinematicObject()) { @@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // virtual bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { - assert(_entity); updateServerPhysicsVariables(); return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } @@ -253,7 +267,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { // This callback is invoked by the physics simulation at the end of each simulation step... // iff the corresponding RigidBody is DYNAMIC and ACTIVE. void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { - assert(_entity); assert(entityTreeIsLocked()); measureBodyAcceleration(); @@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); + if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) { + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); } } - - #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); - qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID(); - qCDebug(physics) << " last edited:" << _entity->getLastEdited() - << formatUsecTime(now - _entity->getLastEdited()) << "ago"; - qCDebug(physics) << " last simulated:" << _entity->getLastSimulated() - << formatUsecTime(now - _entity->getLastSimulated()) << "ago"; - qCDebug(physics) << " last updated:" << _entity->getLastUpdated() - << formatUsecTime(now - _entity->getLastUpdated()) << "ago"; - #endif } @@ -323,19 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) { } } -bool EntityMotionState::isCandidateForOwnership() const { - assert(_body); - assert(_entity); - assert(entityTreeIsLocked()); - return _outgoingPriority != 0 - || isLocallyOwned() - || _entity->dynamicDataNeedsTransmit(); -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { - DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // NOTE: we only get here if we think we own the simulation - assert(_body); + DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); + + // Since we own the simulation: make sure _bidPriority is not less than current owned priority + // because: an _bidPriority of zero indicates that we should drop ownership when we have it. + upgradeBidPriority(_entity->getSimulationPriority()); bool parentTransformSuccess; Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); @@ -347,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); } - // if we've never checked before, our _lastStep will be 0, and we need to initialize our state - if (_lastStep == 0) { - btTransform xform = _body->getWorldTransform(); - _serverVariablesSet = true; - _serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin())); - _serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation()); - _serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma()); - _serverAcceleration = Vectors::ZERO; - _serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity())); - _lastStep = simulationStep; - _serverActionData = _entity->getDynamicData(); - _numInactiveUpdates = 1; - return false; - } - - #ifdef WANT_DEBUG - glm::vec3 wasPosition = _serverPosition; - glm::quat wasRotation = _serverRotation; - glm::vec3 wasAngularVelocity = _serverAngularVelocity; - #endif - int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; @@ -378,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _entity->clearSimulationOwnership(); return false; } - // we resend the inactive update every INACTIVE_UPDATE_PERIOD - // until it is removed from the outgoing updates - // (which happens when we don't own the simulation and it isn't touching our simulation) + // we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates + // until it is removed from the owned list + // (which happens when we no longer own the simulation) const float INACTIVE_UPDATE_PERIOD = 0.5f; return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates); } @@ -390,6 +365,11 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return true; } + if (usecTimestampNow() > _entity->getSimulationOwnershipExpiry()) { + // send update every so often else server will revoke our ownership + return true; + } + _lastStep = simulationStep; if (glm::length2(_serverVelocity) > 0.0f) { // the entity-server doesn't know where avatars are, so it doesn't do simple extrapolation for children of @@ -411,14 +391,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { if (_entity->dynamicDataNeedsTransmit()) { uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; - upgradeOutgoingPriority(priority); + upgradeBidPriority(priority); return true; } - if (_entity->shouldSuppressLocationEdits()) { - return false; - } - // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. @@ -440,13 +416,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) { - #ifdef WANT_DEBUG - qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; - qCDebug(physics) << "wasPosition:" << wasPosition; - qCDebug(physics) << "bullet position:" << position; - qCDebug(physics) << "_serverPosition:" << _serverPosition; - qCDebug(physics) << "dx2:" << dx2; - #endif return true; } } @@ -466,22 +435,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); - #ifdef WANT_DEBUG - if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) { - qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ...."; - - qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity; - qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity; - - qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); - qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity); - - qCDebug(physics) << "wasRotation:" << wasRotation; - qCDebug(physics) << "bullet actualRotation:" << actualRotation; - qCDebug(physics) << "_serverRotation:" << _serverRotation; - } - #endif - return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } @@ -489,14 +442,10 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. - assert(_entity); - assert(_body); assert(entityTreeIsLocked()); - if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { - // don't send updates for someone else's avatarEntities - return false; - } + // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor + assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { return true; @@ -506,44 +455,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { return false; } - if (!isLocallyOwned()) { - // we don't own the simulation - - // NOTE: we do not volunteer to own kinematic or static objects - uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0; - - bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND - usecTimestampNow() > _nextOwnershipBid; // it is time to bid again - if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) { - // we are insufficiently interested so clear _outgoingPriority - // and reset the bid expiry - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; - } - return shouldBid; - } else { - // When we own the simulation: make sure _outgoingPriority is not less than current owned priority - // because: an _outgoingPriority of zero indicates that we should drop ownership when we have it. - upgradeOutgoingPriority(_entity->getSimulationPriority()); - } - return remoteSimulationOutOfSync(simulationStep); } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { - DETAILED_PROFILE_RANGE(simulation_physics, "Send"); - assert(_entity); - assert(entityTreeIsLocked()); - +void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { // make sure all derivatives are zero - zeroCleanObjectVelocities(); - _numInactiveUpdates++; + clearObjectVelocities(); + _numInactiveUpdates = 1; } else { glm::vec3 gravity = _entity->getGravity(); - // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let - // the entity server's estimates include gravity. + // if this entity has been accelerated at close to gravity for a certain number of simulation-steps + // let the entity server's estimates include gravity. const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(gravity); @@ -564,13 +488,80 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - zeroCleanObjectVelocities(); + clearObjectVelocities(); } } _numInactiveUpdates = 0; } +} - // remember properties for local server prediction +void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Bid"); + assert(entityTreeIsLocked()); + + updateSendVelocities(); + + EntityItemProperties properties; + Transform localTransform; + glm::vec3 linearVelocity; + glm::vec3 angularVelocity; + _entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity); + properties.setPosition(localTransform.getTranslation()); + properties.setRotation(localTransform.getRotation()); + properties.setVelocity(linearVelocity); + properties.setAcceleration(_entity->getAcceleration()); + properties.setAngularVelocity(angularVelocity); + if (_entity->dynamicDataNeedsTransmit()) { + _entity->setDynamicDataNeedsTransmit(false); + properties.setActionData(_entity->getDynamicData()); + } + + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); + } + + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); + + // we don't own the simulation for this entity yet, but we're sending a bid for it + uint8_t bidPriority = glm::max(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY); + properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); + // copy _bidPriority into pendingPriority... + _entity->setPendingOwnershipPriority(_bidPriority, now); + + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); + _entity->setLastBroadcast(now); // for debug/physics status icons + + // NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent + // then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary. + + _lastStep = step; + _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; + + // finally: clear _bidPriority + // which will may get promoted before next bid + // or maybe we'll win simulation ownership + _bidPriority = 0; +} + +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Send"); + assert(entityTreeIsLocked()); + assert(isLocallyOwned()); + + updateSendVelocities(); + + // remember _serverFoo data for local prediction of server state Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); @@ -579,11 +570,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _serverActionData = _entity->getDynamicData(); EntityItemProperties properties; - - // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); - properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); @@ -592,61 +580,36 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setActionData(_serverActionData); } - if (properties.transformChanged()) { - if (_entity->updateQueryAACube()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - properties.setQueryAACube(_entity->getQueryAACube()); - } + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); - - #ifdef WANT_DEBUG - quint64 lastSimulated = _entity->getLastSimulated(); - qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() - << "---------------------------------------------"; - qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); - #endif //def WANT_DEBUG + _entity->setSimulationOwnershipExpiry(now + MAX_OUTGOING_SIMULATION_UPDATE_PERIOD); if (_numInactiveUpdates > 0) { - // we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID + // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); - _outgoingPriority = 0; - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - } else if (!isLocallyOwned()) { - // we don't own the simulation for this entity yet, but we're sending a bid for it - quint8 bidPriority = glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); - properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); - _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; - // copy _outgoingPriority into pendingPriority... - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - // don't forget to remember that we have made a bid - _entity->rememberHasSimulationOwnershipBid(); - // ...then reset _outgoingPriority - _outgoingPriority = 0; - // _outgoingPrioriuty will be re-computed before next bid, - // or will be set to agree with ownership priority should we win the bid - } else if (_outgoingPriority != _entity->getSimulationPriority()) { - // we own the simulation but our desired priority has changed - if (_outgoingPriority == 0) { + _bidPriority = 0; + _entity->setPendingOwnershipPriority(_bidPriority, now); + } else if (_bidPriority != _entity->getSimulationPriority()) { + // our desired priority has changed + if (_bidPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to change the priority - properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); + properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority); } - _entity->setPendingOwnershipPriority(_outgoingPriority, now); + _entity->setPendingOwnershipPriority(_bidPriority, now); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; - #endif EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -655,7 +618,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); - _entity->setLastBroadcast(now); + _entity->setLastBroadcast(now); // for debug/physics status icons // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -666,13 +629,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(now); + entityDescendant->setLastBroadcast(now); // for debug/physics status icons } } }); @@ -723,6 +685,10 @@ uint8_t EntityMotionState::getSimulationPriority() const { return _entity->getSimulationPriority(); } +void EntityMotionState::slaveBidPriority() { + upgradeBidPriority(_entity->getSimulationPriority()); +} + // virtual QUuid EntityMotionState::getSimulatorID() const { assert(entityTreeIsLocked()); @@ -731,7 +697,7 @@ QUuid EntityMotionState::getSimulatorID() const { void EntityMotionState::bump(uint8_t priority) { assert(priority != 0); - upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -755,13 +721,14 @@ void EntityMotionState::measureBodyAcceleration() { _lastMeasureStep = thisStep; _measuredDeltaTime = dt; - // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt + // Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt glm::vec3 velocity = getBodyLinearVelocityGTSigma(); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { + // we fall in here when _lastMeasureStep is old: the body has just become active _loopsWithoutOwner = 0; _lastStep = ObjectMotionState::getWorldSimulationStep(); _numInactiveUpdates = 0; @@ -805,24 +772,44 @@ QString EntityMotionState::getName() const { // virtual void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const { - assert(_entity); _entity->computeCollisionGroupAndFinalMask(group, mask); } +bool EntityMotionState::shouldSendBid() { + if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) { + return true; + } else { + // NOTE: this 'else' case has a side-effect: it clears _bidPriority + // which may be updated next simulation step (via collision or script event) + _bidPriority = 0; + return false; + } +} + bool EntityMotionState::isLocallyOwned() const { return _entity->getSimulatorID() == Physics::getSessionUUID(); } -bool EntityMotionState::shouldBeLocallyOwned() const { - return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || +bool EntityMotionState::isLocallyOwnedOrShouldBe() const { + return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) || _entity->getSimulatorID() == Physics::getSessionUUID(); } -void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { - _outgoingPriority = glm::max(_outgoingPriority, priority); +void EntityMotionState::initForBid() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::PendingBid; } -void EntityMotionState::zeroCleanObjectVelocities() const { +void EntityMotionState::initForOwned() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; +} + +void EntityMotionState::upgradeBidPriority(uint8_t priority) { + _bidPriority = glm::max(_bidPriority, priority); +} + +void EntityMotionState::clearObjectVelocities() const { // If transform or velocities are flagged as dirty it means a network or scripted change // occured between the beginning and end of the stepSimulation() and we DON'T want to apply // these physics simulation results. diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 784273d600..a542c61d43 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -24,11 +24,17 @@ class EntityMotionState : public ObjectMotionState { public: + enum class OwnershipState { + NotLocallyOwned = 0, + PendingBid, + LocallyOwned, + Unownable + }; + EntityMotionState() = delete; EntityMotionState(btCollisionShape* shape, EntityItemPointer item); virtual ~EntityMotionState(); - void updateServerPhysicsVariables(); void handleDeactivation(); virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; @@ -44,9 +50,8 @@ public: // this relays outgoing position/rotation to the EntityItem virtual void setWorldTransform(const btTransform& worldTrans) override; - bool isCandidateForOwnership() const; - bool remoteSimulationOutOfSync(uint32_t simulationStep); bool shouldSendUpdate(uint32_t simulationStep); + void sendBid(OctreeEditPacketSender* packetSender, uint32_t step); void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); virtual uint32_t getIncomingDirtyFlags() override; @@ -70,7 +75,9 @@ public: virtual QUuid getSimulatorID() const override; virtual void bump(uint8_t priority) override; - EntityItemPointer getEntity() const { return _entityPtr.lock(); } + // getEntity() returns a smart-pointer by reference because it is only ever used + // to insert into lists of smart pointers, and the lists will make their own copies + const EntityItemPointer& getEntity() const { return _entity; } void resetMeasuredBodyAcceleration(); void measureBodyAcceleration(); @@ -79,15 +86,29 @@ public: virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; + bool shouldSendBid(); bool isLocallyOwned() const override; - bool shouldBeLocallyOwned() const override; + bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() friend class PhysicalEntitySimulation; + OwnershipState getOwnershipState() const { return _ownershipState; } protected: - // changes _outgoingPriority only if priority is larger - void upgradeOutgoingPriority(uint8_t priority); - void zeroCleanObjectVelocities() const; + void updateSendVelocities(); + uint64_t getNextBidExpiry() const { return _nextBidExpiry; } + void initForBid(); + void initForOwned(); + void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; } + void updateServerPhysicsVariables(); + bool remoteSimulationOutOfSync(uint32_t simulationStep); + + // changes _bidPriority only if priority is larger + void upgradeBidPriority(uint8_t priority); + + // upgradeBidPriority to value stored in _entity + void slaveBidPriority(); + + void clearObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; @@ -98,17 +119,21 @@ protected: void setShape(const btCollisionShape* shape) override; void setMotionType(PhysicsMotionType motionType) override; - // In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be - // properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that - // state of affairs we can't keep a real EntityItemPointer as data member (it would produce a - // recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while - // still granting us the capability to generate EntityItemPointers as necessary (for external data - // structures that use the MotionState to get to the EntityItem). - EntityItemWeakPointer _entityPtr; - // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. - EntityItem* _entity; + // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR + // and is only cleared in the DTOR + EntityItemPointer _entity; - bool _serverVariablesSet { false }; + // These "_serverFoo" variables represent what we think the server knows. + // They are used in two different modes: + // + // (1) For remotely owned simulation: we store the last values recieved from the server. + // When the body comes to rest and goes inactive we slam its final transforms to agree with the last server + // update. This to reduce state synchronization errors when the local simulation deviated from remote. + // + // (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time + // according to how we think the server doing it. We calculate the error between the true local transform + // and the remote to decide whether to send another update or not. + // glm::vec3 _serverPosition; // in simulation-frame (not world-frame) glm::quat _serverRotation; glm::vec3 _serverVelocity; @@ -119,16 +144,18 @@ protected: glm::vec3 _lastVelocity; glm::vec3 _measuredAcceleration; - quint64 _nextOwnershipBid { 0 }; + quint64 _nextBidExpiry { 0 }; float _measuredDeltaTime; uint32_t _lastMeasureStep; uint32_t _lastStep; // last step of server extrapolation + OwnershipState _ownershipState { OwnershipState::NotLocallyOwned }; uint8_t _loopsWithoutOwner; mutable uint8_t _accelerationNearlyGravityCount; uint8_t _numInactiveUpdates { 1 }; - uint8_t _outgoingPriority { 0 }; + uint8_t _bidPriority { 0 }; + bool _serverVariablesSet { false }; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index bc68d6de73..9b2da22665 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -47,19 +47,22 @@ ObjectActionTractor::~ObjectActionTractor() { bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, glm::vec3& linearVelocity, glm::vec3& angularVelocity, float& linearTimeScale, float& angularTimeScale) { - SpatiallyNestablePointer other = getOther(); + bool success { true }; + EntityItemPointer other = std::dynamic_pointer_cast(getOther()); withReadLock([&]{ linearTimeScale = _linearTimeScale; angularTimeScale = _angularTimeScale; if (!_otherID.isNull()) { - if (other) { + if (other && other->isReadyToComputeShape()) { rotation = _desiredRotationalTarget * other->getWorldOrientation(); position = other->getWorldOrientation() * _desiredPositionalTarget + other->getWorldPosition(); } else { - // we should have an "other" but can't find it, so disable the tractor. + // we should have an "other" but can't find it, or its collision shape isn't loaded, + // so disable the tractor. linearTimeScale = FLT_MAX; angularTimeScale = FLT_MAX; + success = false; } } else { rotation = _desiredRotationalTarget; @@ -68,7 +71,7 @@ bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, gl linearVelocity = glm::vec3(); angularVelocity = glm::vec3(); }); - return true; + return success; } bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) { @@ -122,6 +125,8 @@ bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) { linearTractorCount++; position += positionForAction; } + } else { + return false; // we don't have both entities loaded, so don't do anything } } diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp index 70613d46ae..4736f2c9e2 100644 --- a/libraries/physics/src/ObjectConstraintBallSocket.cpp +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -85,12 +85,11 @@ btTypedConstraint* ObjectConstraintBallSocket::getConstraint() { return constraint; } - static QString repeatedBallSocketNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( - "ObjectConstraintBallSocket::getConstraint -- no rigidBody.*"); + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { - qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA"); return nullptr; } @@ -99,7 +98,7 @@ btTypedConstraint* ObjectConstraintBallSocket::getConstraint() { btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { - qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyB"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintBallSocket::getConstraint -- no rigidBodyB"); return nullptr; } diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index 86f1f21c63..47228c1c16 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -96,12 +96,11 @@ btTypedConstraint* ObjectConstraintConeTwist::getConstraint() { return constraint; } - static QString repeatedConeTwistNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( - "ObjectConstraintConeTwist::getConstraint -- no rigidBody.*"); + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { - qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA"); return nullptr; } @@ -130,7 +129,7 @@ btTypedConstraint* ObjectConstraintConeTwist::getConstraint() { btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { - qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyB"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintConeTwist::getConstraint -- no rigidBodyB"); return nullptr; } diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 99ddd45abd..4793741391 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -94,13 +94,12 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() { if (constraint) { return constraint; } - - static QString repeatedHingeNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( - "ObjectConstraintHinge::getConstraint -- no rigidBody.*"); + + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { - qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyA"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintHinge::getConstraint -- no rigidBodyA"); return nullptr; } @@ -115,7 +114,7 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() { // This hinge is between two entities... find the other rigid body. btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { - qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyB"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintHinge::getConstraint -- no rigidBodyB"); return nullptr; } diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index c236afc10d..da5bba7f4d 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -87,12 +87,11 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() { return constraint; } - static QString repeatedSliderNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( - "ObjectConstraintSlider::getConstraint -- no rigidBody.*"); + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { - qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyA"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintSlider::getConstraint -- no rigidBodyA"); return nullptr; } @@ -121,7 +120,7 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() { btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { - qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyB"; + HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintSlider::getConstraint -- no rigidBodyB"); return nullptr; } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 7f583ca9ca..fbda9366fc 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -148,9 +148,9 @@ public: virtual const QUuid getObjectID() const = 0; - virtual quint8 getSimulationPriority() const { return 0; } + virtual uint8_t getSimulationPriority() const { return 0; } virtual QUuid getSimulatorID() const = 0; - virtual void bump(quint8 priority) {} + virtual void bump(uint8_t priority) {} virtual QString getName() const { return ""; } @@ -164,7 +164,7 @@ public: void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } virtual bool isLocallyOwned() const { return false; } - virtual bool shouldBeLocallyOwned() const { return false; } + virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() friend class PhysicsEngine; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index e4ba47e205..ab7c2ec252 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -40,8 +40,8 @@ void PhysicalEntitySimulation::init( } // begin EntitySimulation overrides -void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { - // Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere. +void PhysicalEntitySimulation::updateEntitiesInternal(uint64_t now) { + // Do nothing here because the "internal" update the PhysicsEngine::stepSimulation() which is done elsewhere. } void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { @@ -61,33 +61,58 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { if (entity->isSimulated()) { EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); _entitiesToAddToPhysics.remove(entity); EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); - } else { - _entitiesToDelete.insert(entity); + } else if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); } } } -void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { - QMutexLocker lock(&_mutex); - for (auto entity : _entitiesToDelete) { - // this entity is still in its tree, so we insert into the external list - entitiesToDelete.push_back(entity); +void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { + assert(motionState); + if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) { + for (uint32_t i = 0; i < _owned.size(); ++i) { + if (_owned[i] == motionState) { + _owned[i]->clearOwnershipState(); + _owned.remove(i); + } + } + } else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) { + for (uint32_t i = 0; i < _bids.size(); ++i) { + if (_bids[i] == motionState) { + _bids[i]->clearOwnershipState(); + _bids.remove(i); + } + } + } +} - // Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo - // rather than do it here +void PhysicalEntitySimulation::clearOwnershipData() { + for (uint32_t i = 0; i < _owned.size(); ++i) { + _owned[i]->clearOwnershipState(); + } + _owned.clear(); + for (uint32_t i = 0; i < _bids.size(); ++i) { + _bids[i]->clearOwnershipState(); + } + _bids.clear(); +} + +void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) { + QMutexLocker lock(&_mutex); + for (auto entity : _deadEntities) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { _entitiesToRemoveFromPhysics.insert(entity); } } - _entitiesToDelete.clear(); + _deadEntities.swap(deadEntities); + _deadEntities.clear(); } void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { @@ -98,15 +123,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { if (motionState) { if (!entity->shouldBePhysical()) { // the entity should be removed from the physical simulation - _pendingChanges.remove(motionState); + _incomingChanges.remove(motionState); _physicalObjects.remove(motionState); - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); if (entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } } else { - _pendingChanges.insert(motionState); + _incomingChanges.insert(motionState); } } else if (entity->shouldBePhysical()) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. @@ -125,80 +150,68 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // while it is in the middle of a simulation step. As it is, we're probably in shutdown mode // anyway, so maybe the simulation was already properly shutdown? Cross our fingers... - // copy everything into _entitiesToDelete - for (auto stateItr : _physicalObjects) { - EntityMotionState* motionState = static_cast(&(*stateItr)); - _entitiesToDelete.insert(motionState->getEntity()); - } - - // then remove the objects (aka MotionStates) from physics + // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); // delete the MotionStates - // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete - // its own PhysicsInfo rather than do it here - for (auto entity : _entitiesToDelete) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - entity->setPhysicsInfo(nullptr); - delete motionState; - } + for (auto stateItr : _physicalObjects) { + EntityMotionState* motionState = static_cast(&(*stateItr)); + assert(motionState); + EntityItemPointer entity = motionState->getEntity(); + // TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // until then we must do it here + delete motionState; } - - // finally clear all lists maintained by this class _physicalObjects.clear(); + + // clear all other lists specific to this derived class + clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); - _entitiesToRelease.clear(); _entitiesToAddToPhysics.clear(); - _pendingChanges.clear(); - _outgoingChanges.clear(); + _incomingChanges.clear(); } // virtual void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { assert(entity); assert(entity->isDead()); + QMutexLocker lock(&_mutex); entity->clearActions(getThisPointer()); removeEntityInternal(entity); } // end EntitySimulation overrides -void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { - result.clear(); +const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() { QMutexLocker lock(&_mutex); for (auto entity: _entitiesToRemoveFromPhysics) { - // make sure it isn't on any side lists - _entitiesToAddToPhysics.remove(entity); - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - _pendingChanges.remove(motionState); - _outgoingChanges.remove(motionState); - _physicalObjects.remove(motionState); - result.push_back(motionState); - _entitiesToRelease.insert(entity); + assert(motionState); + + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); } - if (entity->isDead()) { - _entitiesToDelete.insert(entity); - } + _incomingChanges.remove(motionState); + removeOwnershipData(motionState); + _physicalObjects.remove(motionState); + + // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) + _objectsToDelete.push_back(motionState); } _entitiesToRemoveFromPhysics.clear(); + return _objectsToDelete; } void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() { QMutexLocker lock(&_mutex); - for (auto entity: _entitiesToRelease) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - assert(motionState); - entity->setPhysicsInfo(nullptr); + for (auto motionState : _objectsToDelete) { + // someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // until then we must do it here + // NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor delete motionState; - - if (entity->isDead()) { - _entitiesToDelete.insert(entity); - } } - _entitiesToRelease.clear(); + _objectsToDelete.clear(); } void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { @@ -248,18 +261,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) { QMutexLocker lock(&_mutex); for (auto object : objectsToChange) { - _pendingChanges.insert(static_cast(object)); + _incomingChanges.insert(static_cast(object)); } } void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - for (auto stateItr : _pendingChanges) { + for (auto stateItr : _incomingChanges) { EntityMotionState* motionState = &(*stateItr); result.push_back(motionState); } - _pendingChanges.clear(); + _incomingChanges.clear(); } void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) { @@ -279,20 +292,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); - // walk the motionStates looking for those that correspond to entities - { - PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - EntityItemPointer entity = entityState->getEntity(); - assert(entity.get()); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + _entitiesToSort.insert(entityState->getEntity()); + if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) { + // NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in + // and is distinct from entityState->isLocallyOwned() which checks the simulation ownership + // properties of the corresponding EntityItem. It is possible for the two states to be out + // of sync. In fact, we're trying to put them back into sync here. + if (entityState->isLocallyOwned()) { + addOwnership(entityState); + } else if (entityState->shouldSendBid()) { + addOwnershipBid(entityState); } - _entitiesToSort.insert(entity); } } } @@ -302,26 +317,78 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta _lastStepSendPackets = numSubsteps; if (Physics::getSessionUUID().isNull()) { - // usually don't get here, but if so --> nothing to do - _outgoingChanges.clear(); - return; + // usually don't get here, but if so clear all ownership + clearOwnershipData(); } + // send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list + sendOwnedUpdates(numSubsteps); + sendOwnershipBids(numSubsteps); + } +} - // look for entities to prune or update - PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); - QSet::iterator stateItr = _outgoingChanges.begin(); - while (stateItr != _outgoingChanges.end()) { - EntityMotionState* state = *stateItr; - if (!state->isCandidateForOwnership()) { - // prune - stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps)) { - // update - state->sendUpdate(_entityPacketSender, numSubsteps); - ++stateItr; - } else { - ++stateItr; +void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) { + motionState->initForBid(); + motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps()); + _bids.push_back(motionState); + _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry()); +} + +void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) { + motionState->initForOwned(); + _owned.push_back(motionState); +} + +void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) { + uint64_t now = usecTimestampNow(); + if (now > _nextBidExpiry) { + PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size()); + _nextBidExpiry = std::numeric_limits::max(); + uint32_t i = 0; + while (i < _bids.size()) { + bool removeBid = false; + if (_bids[i]->isLocallyOwned()) { + // when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored + // in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h) + // therefore we need to immediately send an update so that the values stored are what we're + // "telling" the server rather than what we've been "hearing" from the server. + _bids[i]->slaveBidPriority(); + _bids[i]->sendUpdate(_entityPacketSender, numSubsteps); + + addOwnership(_bids[i]); + removeBid = true; + } else if (!_bids[i]->shouldSendBid()) { + removeBid = true; + _bids[i]->clearOwnershipState(); } + if (removeBid) { + _bids.remove(i); + } else { + if (now > _bids[i]->getNextBidExpiry()) { + _bids[i]->sendBid(_entityPacketSender, numSubsteps); + _nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry()); + } + ++i; + } + } + } +} + +void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) { + PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size()); + uint32_t i = 0; + while (i < _owned.size()) { + if (!_owned[i]->isLocallyOwned()) { + if (_owned[i]->shouldSendBid()) { + addOwnershipBid(_owned[i]); + } else { + _owned[i]->clearOwnershipState(); + } + _owned.remove(i); + } else { + if (_owned[i]->shouldSendUpdate(numSubsteps)) { + _owned[i]->sendUpdate(_entityPacketSender, numSubsteps); + } + ++i; } } } @@ -336,7 +403,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll } } - void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) { if (_physicsEngine) { // FIXME put fine grain locking into _physicsEngine diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index b9acf4cace..7b6fe221fb 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -27,7 +27,19 @@ class PhysicalEntitySimulation; using PhysicalEntitySimulationPointer = std::shared_ptr; using SetOfEntityMotionStates = QSet; +class VectorOfEntityMotionStates: public std::vector { +public: + void remove(uint32_t index) { + assert(index < size()); + if (index < size() - 1) { + (*this)[index] = back(); + } + pop_back(); + } +}; + class PhysicalEntitySimulation : public EntitySimulation { + Q_OBJECT public: PhysicalEntitySimulation(); ~PhysicalEntitySimulation(); @@ -37,21 +49,28 @@ public: virtual void addDynamic(EntityDynamicPointer dynamic) override; virtual void applyDynamicChanges() override; - virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override; + virtual void takeDeadEntities(SetOfEntities& deadEntities) override; + +signals: + void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); protected: // only called by EntitySimulation // overrides for EntitySimulation - virtual void updateEntitiesInternal(const quint64& now) override; + virtual void updateEntitiesInternal(uint64_t now) override; virtual void addEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void clearEntitiesInternal() override; + void removeOwnershipData(EntityMotionState* motionState); + void clearOwnershipData(); + public: virtual void prepareEntityForDelete(EntityItemPointer entity) override; - void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result); + const VectorOfMotionStates& getObjectsToRemoveFromPhysics(); void deleteObjectsRemovedFromPhysics(); + void getObjectsToAddToPhysics(VectorOfMotionStates& result); void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void getObjectsToChange(VectorOfMotionStates& result); @@ -62,19 +81,28 @@ public: EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } -private: - SetOfEntities _entitiesToRemoveFromPhysics; - SetOfEntities _entitiesToRelease; - SetOfEntities _entitiesToAddToPhysics; + void addOwnershipBid(EntityMotionState* motionState); + void addOwnership(EntityMotionState* motionState); + void sendOwnershipBids(uint32_t numSubsteps); + void sendOwnedUpdates(uint32_t numSubsteps); - SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed - SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server +private: + SetOfEntities _entitiesToAddToPhysics; + SetOfEntities _entitiesToRemoveFromPhysics; + + VectorOfMotionStates _objectsToDelete; + + SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources + // and need their RigidBodies updated SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine PhysicsEnginePointer _physicsEngine = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr; + VectorOfEntityMotionStates _owned; + VectorOfEntityMotionStates _bids; + uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 2d84f0cef1..7c2ad55405 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -413,7 +413,7 @@ void PhysicsEngine::harvestPerformanceStats() { if (QString(itr->Get_Current_Name()) == "stepSimulation") { itr->Enter_Child(childIndex); StatsHarvester harvester; - harvester.recurse(itr, "step/"); + harvester.recurse(itr, "physics/"); break; } itr->Next(); @@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { // modify the logic below. // // We only create events when at least one of the objects is (or should be) owned in the local simulation. - if (motionStateA && (motionStateA->shouldBeLocallyOwned())) { + if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) { QUuid idA = motionStateA->getObjectID(); QUuid idB; if (motionStateB) { @@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { (motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f)); glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); - } else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) { + } else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) { QUuid idB = motionStateB->getObjectID(); QUuid idA; if (motionStateA) { diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 5307e17355..031baece5f 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,8 +68,9 @@ void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { auto pickResult = getPrevPickResult(); - updateVisuals(pickResult); - generatePointerEvents(pointerID, pickResult); + auto visualPickResult = getVisualPickResult(pickResult); + updateVisuals(visualPickResult); + generatePointerEvents(pointerID, visualPickResult); }); } diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 3197c80cad..0c842dbd88 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -89,6 +89,7 @@ protected: virtual bool shouldHover(const PickResultPointer& pickResult) { return true; } virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; } + virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; }; static const float POINTER_MOVE_DELAY; static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index d8b72c8838..c155d5bd7f 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -219,7 +219,29 @@ bool Procedural::isReady() const { return true; } -void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation) { +std::string Procedural::replaceProceduralBlock(const std::string& fragmentSource) { + std::string fragmentShaderSource = fragmentSource; + size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); + if (replaceIndex != std::string::npos) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag::getSource()); + } + + replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); + if (replaceIndex != std::string::npos) { + if (_data.version == 1) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V1 1"); + } else if (_data.version == 2) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V2 1"); + } + } + replaceIndex = fragmentShaderSource.find(PROCEDURAL_BLOCK); + if (replaceIndex != std::string::npos) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); + } + return fragmentShaderSource; +} + +void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const glm::vec4& color) { _entityDimensions = size; _entityPosition = position; _entityOrientation = glm::mat3_cast(orientation); @@ -242,58 +264,48 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm } // Build the fragment shader - std::string fragmentShaderSource = _fragmentSource; - size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); - if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag::getSource()); - } - - replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); - if (replaceIndex != std::string::npos) { - if (_data.version == 1) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V1 1"); - } else if (_data.version == 2) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V2 1"); - } - } - replaceIndex = fragmentShaderSource.find(PROCEDURAL_BLOCK); - if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); - } + std::string opaqueShaderSource = replaceProceduralBlock(_opaquefragmentSource); + std::string transparentShaderSource = replaceProceduralBlock(_transparentfragmentSource); // Leave this here for debugging // qCDebug(procedural) << "FragmentShader:\n" << fragmentShaderSource.c_str(); - _fragmentShader = gpu::Shader::createPixel(fragmentShaderSource); - _shader = gpu::Shader::createProgram(_vertexShader, _fragmentShader); + _opaqueFragmentShader = gpu::Shader::createPixel(opaqueShaderSource); + _opaqueShader = gpu::Shader::createProgram(_vertexShader, _opaqueFragmentShader); + _transparentFragmentShader = gpu::Shader::createPixel(transparentShaderSource); + _transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("iChannel0"), 0)); slotBindings.insert(gpu::Shader::Binding(std::string("iChannel1"), 1)); slotBindings.insert(gpu::Shader::Binding(std::string("iChannel2"), 2)); slotBindings.insert(gpu::Shader::Binding(std::string("iChannel3"), 3)); - gpu::Shader::makeProgram(*_shader, slotBindings); + gpu::Shader::makeProgram(*_opaqueShader, slotBindings); + gpu::Shader::makeProgram(*_transparentShader, slotBindings); - _opaquePipeline = gpu::Pipeline::create(_shader, _opaqueState); - _transparentPipeline = gpu::Pipeline::create(_shader, _transparentState); + _opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState); + _transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState); for (size_t i = 0; i < NUM_STANDARD_UNIFORMS; ++i) { const std::string& name = STANDARD_UNIFORM_NAMES[i]; - _standardUniformSlots[i] = _shader->getUniforms().findLocation(name); + _standardOpaqueUniformSlots[i] = _opaqueShader->getUniforms().findLocation(name); + _standardTransparentUniformSlots[i] = _transparentShader->getUniforms().findLocation(name); } _start = usecTimestampNow(); _frameCount = 0; } - batch.setPipeline(isFading() ? _transparentPipeline : _opaquePipeline); + bool transparent = color.a < 1.0f; + batch.setPipeline(transparent ? _transparentPipeline : _opaquePipeline); - if (_shaderDirty || _uniformsDirty) { - setupUniforms(); + if (_shaderDirty || _uniformsDirty || _prevTransparent != transparent) { + setupUniforms(transparent); } - if (_shaderDirty || _uniformsDirty || _channelsDirty) { - setupChannels(_shaderDirty || _uniformsDirty); + if (_shaderDirty || _uniformsDirty || _channelsDirty || _prevTransparent != transparent) { + setupChannels(_shaderDirty || _uniformsDirty, transparent); } + _prevTransparent = transparent; _shaderDirty = _uniformsDirty = _channelsDirty = false; for (auto lambda : _uniforms) { @@ -319,12 +331,12 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm } } -void Procedural::setupUniforms() { +void Procedural::setupUniforms(bool transparent) { _uniforms.clear(); // Set any userdata specified uniforms foreach(QString key, _data.uniforms.keys()) { std::string uniformName = key.toLocal8Bit().data(); - int32_t slot = _shader->getUniforms().findLocation(uniformName); + int32_t slot = (transparent ? _transparentShader : _opaqueShader)->getUniforms().findLocation(uniformName); if (gpu::Shader::INVALID_LOCATION == slot) { continue; } @@ -385,15 +397,17 @@ void Procedural::setupUniforms() { } } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[TIME]) { + auto uniformSlots = transparent ? _standardTransparentUniformSlots : _standardOpaqueUniformSlots; + + if (gpu::Shader::INVALID_LOCATION != uniformSlots[TIME]) { _uniforms.push_back([=](gpu::Batch& batch) { // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; - batch._glUniform(_standardUniformSlots[TIME], time); + batch._glUniform(uniformSlots[TIME], time); }); } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[DATE]) { + if (gpu::Shader::INVALID_LOCATION != uniformSlots[DATE]) { _uniforms.push_back([=](gpu::Batch& batch) { QDateTime now = QDateTime::currentDateTimeUtc(); QDate date = now.date(); @@ -406,40 +420,41 @@ void Procedural::setupUniforms() { v.z = date.day(); float fractSeconds = (time.msec() / 1000.0f); v.w = (time.hour() * 3600) + (time.minute() * 60) + time.second() + fractSeconds; - batch._glUniform(_standardUniformSlots[DATE], v); + batch._glUniform(uniformSlots[DATE], v); }); } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[FRAME_COUNT]) { + if (gpu::Shader::INVALID_LOCATION != uniformSlots[FRAME_COUNT]) { _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(_standardUniformSlots[FRAME_COUNT], ++_frameCount); + batch._glUniform(uniformSlots[FRAME_COUNT], ++_frameCount); }); } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[SCALE]) { + if (gpu::Shader::INVALID_LOCATION != uniformSlots[SCALE]) { // FIXME move into the 'set once' section, since this doesn't change over time _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(_standardUniformSlots[SCALE], _entityDimensions); + batch._glUniform(uniformSlots[SCALE], _entityDimensions); }); } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[ORIENTATION]) { + if (gpu::Shader::INVALID_LOCATION != uniformSlots[ORIENTATION]) { // FIXME move into the 'set once' section, since this doesn't change over time _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(_standardUniformSlots[ORIENTATION], _entityOrientation); + batch._glUniform(uniformSlots[ORIENTATION], _entityOrientation); }); } - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[POSITION]) { + if (gpu::Shader::INVALID_LOCATION != uniformSlots[POSITION]) { // FIXME move into the 'set once' section, since this doesn't change over time _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(_standardUniformSlots[POSITION], _entityPosition); + batch._glUniform(uniformSlots[POSITION], _entityPosition); }); } } -void Procedural::setupChannels(bool shouldCreate) { - if (gpu::Shader::INVALID_LOCATION != _standardUniformSlots[CHANNEL_RESOLUTION]) { +void Procedural::setupChannels(bool shouldCreate, bool transparent) { + auto uniformSlots = transparent ? _standardTransparentUniformSlots : _standardOpaqueUniformSlots; + if (gpu::Shader::INVALID_LOCATION != uniformSlots[CHANNEL_RESOLUTION]) { if (!shouldCreate) { // Instead of modifying the last element, just remove and recreate it. _uniforms.pop_back(); @@ -451,7 +466,7 @@ void Procedural::setupChannels(bool shouldCreate) { channelSizes[i] = vec3(_channels[i]->getWidth(), _channels[i]->getHeight(), 1.0); } } - batch._glUniform3fv(_standardUniformSlots[CHANNEL_RESOLUTION], MAX_PROCEDURAL_TEXTURE_CHANNELS, &channelSizes[0].x); + batch._glUniform3fv(uniformSlots[CHANNEL_RESOLUTION], MAX_PROCEDURAL_TEXTURE_CHANNELS, &channelSizes[0].x); }); } } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 9c29a681f0..1d3b0b3b5a 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -55,8 +55,8 @@ public: bool isReady() const; bool isEnabled() const { return _enabled; } - void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation); - const gpu::ShaderPointer& getShader() const { return _shader; } + void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const glm::vec4& color = glm::vec4(1)); + const gpu::ShaderPointer& getOpaqueShader() const { return _opaqueShader; } glm::vec4 getColor(const glm::vec4& entityColor); quint64 getFadeStartTime() const { return _fadeStartTime; } @@ -65,7 +65,8 @@ public: void setDoesFade(bool doesFade) { _doesFade = doesFade; } std::string _vertexSource; - std::string _fragmentSource; + std::string _opaquefragmentSource; + std::string _transparentfragmentSource; gpu::StatePointer _opaqueState { std::make_shared() }; gpu::StatePointer _transparentState { std::make_shared() }; @@ -101,13 +102,16 @@ protected: // Rendering objects UniformLambdas _uniforms; - int32_t _standardUniformSlots[NUM_STANDARD_UNIFORMS]; + int32_t _standardOpaqueUniformSlots[NUM_STANDARD_UNIFORMS]; + int32_t _standardTransparentUniformSlots[NUM_STANDARD_UNIFORMS]; NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; gpu::PipelinePointer _opaquePipeline; gpu::PipelinePointer _transparentPipeline; gpu::ShaderPointer _vertexShader; - gpu::ShaderPointer _fragmentShader; - gpu::ShaderPointer _shader; + gpu::ShaderPointer _opaqueFragmentShader; + gpu::ShaderPointer _transparentFragmentShader; + gpu::ShaderPointer _opaqueShader; + gpu::ShaderPointer _transparentShader; // Entity metadata glm::vec3 _entityDimensions; @@ -116,13 +120,16 @@ protected: private: // This should only be called from the render thread, as it shares data with Procedural::prepare - void setupUniforms(); - void setupChannels(bool shouldCreate); + void setupUniforms(bool transparent); + void setupChannels(bool shouldCreate, bool transparent); + + std::string replaceProceduralBlock(const std::string& fragmentSource); mutable quint64 _fadeStartTime { 0 }; mutable bool _hasStartedFade { false }; mutable bool _isFading { false }; bool _doesFade { true }; + bool _prevTransparent { false }; }; #endif diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 43633e47ad..0c6501928b 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -20,7 +20,7 @@ ProceduralSkybox::ProceduralSkybox() : graphics::Skybox() { _procedural._vertexSource = skybox_vert::getSource(); - _procedural._fragmentSource = skybox_frag::getSource(); + _procedural._opaquefragmentSource = skybox_frag::getSource(); // Adjust the pipeline state for background using the stencil test _procedural.setDoesFade(false); // Must match PrepareStencil::STENCIL_BACKGROUND @@ -61,8 +61,8 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, auto& procedural = skybox._procedural; procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat()); - auto textureSlot = procedural.getShader()->getTextures().findLocation("cubeMap"); - auto bufferSlot = procedural.getShader()->getUniformBuffers().findLocation("skyboxBuffer"); + auto textureSlot = procedural.getOpaqueShader()->getTextures().findLocation("cubeMap"); + auto bufferSlot = procedural.getOpaqueShader()->getUniformBuffers().findLocation("skyboxBuffer"); skybox.prepare(batch, textureSlot, bufferSlot); batch.draw(gpu::TRIANGLE_STRIP, 4); } diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 6580c5b1e8..2da1c41340 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -27,6 +27,8 @@ #include "impl/SharedObject.h" #include "impl/TextureCache.h" +#include "Profile.h" + using namespace hifi::qml; using namespace hifi::qml::impl; @@ -166,18 +168,30 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) case QEvent::TouchUpdate: case QEvent::TouchEnd: { QTouchEvent *originalEvent = static_cast(event); - QTouchEvent fakeEvent(*originalEvent); - auto newTouchPoints = fakeEvent.touchPoints(); - for (size_t i = 0; i < newTouchPoints.size(); ++i) { - const auto &originalPoint = originalEvent->touchPoints()[i]; - auto &newPoint = newTouchPoints[i]; - newPoint.setPos(originalPoint.pos()); + QEvent::Type fakeMouseEventType = QEvent::None; + Qt::MouseButton fakeMouseButton = Qt::LeftButton; + Qt::MouseButtons fakeMouseButtons = Qt::NoButton; + switch (event->type()) { + case QEvent::TouchBegin: + fakeMouseEventType = QEvent::MouseButtonPress; + fakeMouseButtons = Qt::LeftButton; + break; + case QEvent::TouchUpdate: + fakeMouseEventType = QEvent::MouseMove; + fakeMouseButtons = Qt::LeftButton; + break; + case QEvent::TouchEnd: + fakeMouseEventType = QEvent::MouseButtonRelease; + fakeMouseButtons = Qt::NoButton; + break; } - fakeEvent.setTouchPoints(newTouchPoints); - if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeEvent)) { - qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeEvent.type() - << "_quickWindow handled it... accepted:" << fakeEvent.isAccepted(); - return false; //event->isAccepted(); + // Same case as OffscreenUi.cpp::eventFilter: touch events are always being accepted so we now use mouse events and consider one touch, touchPoints()[0]. + QMouseEvent fakeMouseEvent(fakeMouseEventType, originalEvent->touchPoints()[0].pos(), fakeMouseButton, fakeMouseButtons, Qt::NoModifier); + fakeMouseEvent.ignore(); + if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeMouseEvent)) { + /*qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeMouseEvent.type() + << "_quickWindow handled it... accepted:" << fakeMouseEvent.isAccepted();*/ + return fakeMouseEvent.isAccepted(); } break; } @@ -272,6 +286,7 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { + PROFILE_RANGE_EX(app, "OffscreenSurface::loadInternal", 0xffff00ff, 0, { std::make_pair("url", qmlSource.toDisplayString()) }); if (QThread::currentThread() != thread()) { qFatal("Called load on a non-surface thread"); } @@ -292,7 +307,11 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, } auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); - auto qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); + QQmlComponent* qmlComponent; + { + PROFILE_RANGE(app, "new QQmlComponent"); + qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); + } if (qmlComponent->isLoading()) { connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { finishQmlLoad(qmlComponent, targetContext, parent, callback); }); @@ -306,6 +325,7 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { + PROFILE_RANGE(app, "finishQmlLoad"); disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); if (qmlComponent->isError()) { for (const auto& error : qmlComponent->errors()) { diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index f2af5bd036..b2057117f6 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -37,9 +37,8 @@ static const int MIN_TIMER_MS = 5; using namespace hifi::qml; using namespace hifi::qml::impl; -TextureCache offscreenTextures; - TextureCache& SharedObject::getTextureCache() { + static TextureCache offscreenTextures; return offscreenTextures; } @@ -69,6 +68,7 @@ SharedObject::SharedObject() { _quickWindow->setColor(QColor(255, 255, 255, 0)); _quickWindow->setClearBeforeRendering(true); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } @@ -105,7 +105,10 @@ void SharedObject::create(OffscreenSurface* surface) { // Create a QML engine. auto qmlEngine = acquireEngine(surface); - _qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine); + { + PROFILE_RANGE(startup, "new QQmlContext"); + _qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine); + } surface->onRootContextCreated(_qmlContext); emit surface->rootContextCreated(_qmlContext); @@ -124,6 +127,7 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); + // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -152,9 +156,16 @@ void SharedObject::destroy() { QObject::disconnect(_renderControl); QObject::disconnect(qApp); - QMutexLocker lock(&_mutex); - _quit = true; - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit)); + { + QMutexLocker lock(&_mutex); + _quit = true; + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + } + // Block until the rendering thread has stopped + // FIXME this is undesirable because this is blocking the main thread, + // but I haven't found a reliable way to do this only at application + // shutdown + _renderThread->wait(); } @@ -167,6 +178,7 @@ static size_t globalEngineRefCount{ 0 }; #endif QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { + PROFILE_RANGE(startup, "acquireEngine"); Q_ASSERT(QThread::currentThread() == qApp->thread()); QQmlEngine* result = nullptr; @@ -234,7 +246,7 @@ void SharedObject::releaseTextureAndFence() { QMutexLocker lock(&_mutex); // If the most recent texture was unused, we can directly recycle it if (_latestTextureAndFence.first) { - offscreenTextures.releaseTexture(_latestTextureAndFence); + getTextureCache().releaseTexture(_latestTextureAndFence); _latestTextureAndFence = TextureAndFence{ 0, 0 }; } } @@ -298,7 +310,10 @@ bool SharedObject::preRender() { void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size) { QMutexLocker locker(&_mutex); if (size != QSize(0, 0)) { - offscreenTextures.releaseSize(size); + getTextureCache().releaseSize(size); + if (_latestTextureAndFence.first) { + getTextureCache().releaseTexture(_latestTextureAndFence); + } } _renderControl->invalidate(); canvas.doneCurrent(); @@ -394,7 +409,7 @@ void SharedObject::onRender() { } void SharedObject::onTimer() { - offscreenTextures.report(); + getTextureCache().report(); if (!_renderRequested) { return; } @@ -427,7 +442,7 @@ void SharedObject::updateTextureAndFence(const TextureAndFence& newTextureAndFen QMutexLocker locker(&_mutex); // If the most recent texture was unused, we can directly recycle it if (_latestTextureAndFence.first) { - offscreenTextures.releaseTexture(_latestTextureAndFence); + getTextureCache().releaseTexture(_latestTextureAndFence); _latestTextureAndFence = { 0, 0 }; } diff --git a/libraries/qml/src/qml/impl/TextureCache.cpp b/libraries/qml/src/qml/impl/TextureCache.cpp index c649a36594..7af8fa1ac9 100644 --- a/libraries/qml/src/qml/impl/TextureCache.cpp +++ b/libraries/qml/src/qml/impl/TextureCache.cpp @@ -11,10 +11,6 @@ using namespace hifi::qml::impl; -#if defined(Q_OS_ANDROID) -#define USE_GLES 1 -#endif - uint64_t uvec2ToUint64(const QSize& size) { uint64_t result = size.width(); result <<= 32; @@ -31,26 +27,23 @@ void TextureCache::acquireSize(const QSize& size) { void TextureCache::releaseSize(const QSize& size) { auto sizeKey = uvec2ToUint64(size); - ValueList texturesToDelete; { Lock lock(_mutex); assert(_textures.count(sizeKey)); auto& textureSet = _textures[sizeKey]; if (0 == --textureSet.clientCount) { - texturesToDelete.swap(textureSet.returnedTextures); + for (const auto& textureAndFence : textureSet.returnedTextures) { + destroy(textureAndFence); + } _textures.erase(sizeKey); } } - for (const auto& textureAndFence : texturesToDelete) { - destroy(textureAndFence); - } } uint32_t TextureCache::acquireTexture(const QSize& size) { Lock lock(_mutex); recycle(); - ++_activeTextureCount; auto sizeKey = uvec2ToUint64(size); assert(_textures.count(sizeKey)); @@ -83,7 +76,12 @@ void TextureCache::report() { } size_t TextureCache::getUsedTextureMemory() { - return _totalTextureUsage; + size_t toReturn; + { + Lock lock(_mutex); + toReturn = _totalTextureUsage; + } + return toReturn; } size_t TextureCache::getMemoryForSize(const QSize& size) { @@ -122,8 +120,6 @@ uint32_t TextureCache::createTexture(const QSize& size) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); #if !defined(USE_GLES) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); #endif diff --git a/libraries/qml/src/qml/impl/TextureCache.h b/libraries/qml/src/qml/impl/TextureCache.h index 572e1cadea..c146d0bdbf 100644 --- a/libraries/qml/src/qml/impl/TextureCache.h +++ b/libraries/qml/src/qml/impl/TextureCache.h @@ -41,7 +41,6 @@ public: ValueList returnedTextures; }; - void releaseSize(const QSize& size); void acquireSize(const QSize& size); uint32_t acquireTexture(const QSize& size); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 4a9b69c099..ba5036ad68 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -380,6 +380,8 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr); batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr); }); + + args->popViewFrustum(); } @@ -520,7 +522,7 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) { viewFrustum.setProjection(projMat); viewFrustum.calculate(); - args->setViewFrustum(viewFrustum); + args->pushViewFrustum(viewFrustum); } else { mat4 projMats[2]; args->_context->getStereoProjections(projMats); @@ -538,4 +540,4 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) { } -#endif \ No newline at end of file +#endif diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 886795ec79..d9115c7943 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -126,9 +126,6 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, if (defaultSkyboxAmbientTexture) { sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance()); sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture); - } else { - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( - "Failed to get a valid Default Skybox Ambient Texture ? probably because it couldn't be find during initialization step"); } // fall through: render defaults skybox } else { diff --git a/libraries/render-utils/src/DefaultMaterials.slh b/libraries/render-utils/src/DefaultMaterials.slh new file mode 100644 index 0000000000..afb3dc2b28 --- /dev/null +++ b/libraries/render-utils/src/DefaultMaterials.slh @@ -0,0 +1,23 @@ + +<@if not DEFAULT_MATERIAL_SLH@> +<@def DEFAULT_MATERIAL_SLH@> + +const float DEFAULT_ROUGHNESS = 0.9; +const float DEFAULT_SHININESS = 10.0; +const float DEFAULT_METALLIC = 0.0; +const vec3 DEFAULT_SPECULAR = vec3(0.1); +const vec3 DEFAULT_EMISSIVE = vec3(0.0); +const float DEFAULT_OCCLUSION = 1.0; +const float DEFAULT_SCATTERING = 0.0; +const vec3 DEFAULT_FRESNEL = DEFAULT_EMISSIVE; + +<@endif@> diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 3cbed3fcef..f7ab0957cc 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -118,7 +118,7 @@ DeferredFragment unpackDeferredFragmentNoPositionNoAmbient(vec2 texcoord) { <@include DeferredTransform.slh@> <$declareDeferredFrameTransform()$> -vec4 unpackDeferredPosition(DeferredFrameTransform deferredTransform, float depthValue, vec2 texcoord) { +vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { int side = 0; if (isStereo()) { if (texcoord.x > 0.5) { @@ -127,9 +127,14 @@ vec4 unpackDeferredPosition(DeferredFrameTransform deferredTransform, float dept } texcoord.x *= 2.0; } - float Zeye = evalZeyeFromZdb(depthValue); - return vec4(evalEyePositionFromZeye(side, Zeye, texcoord), 1.0); + return vec4(evalEyePositionFromZdb(side, depthValue, texcoord), 1.0); +} + +// This method to unpack position is fastesst +vec4 unpackDeferredPositionFromZdb(vec2 texcoord) { + float Zdb = texture(depthMap, texcoord).x; + return unpackDeferredPosition(Zdb, texcoord); } vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { @@ -152,7 +157,7 @@ DeferredFragment unpackDeferredFragment(DeferredFrameTransform deferredTransform DeferredFragment frag = unpackDeferredFragmentNoPosition(texcoord); frag.depthVal = depthValue; - frag.position = unpackDeferredPosition(deferredTransform, frag.depthVal, texcoord); + frag.position = unpackDeferredPosition(frag.depthVal, texcoord); return frag; } diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index 2f6bfaeb61..49db49af2a 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -26,14 +26,7 @@ float evalOpaqueFinalAlpha(float alpha, float mapAlpha) { return mix(alpha, 1.0 - alpha, step(mapAlpha, alphaThreshold)); } -const float DEFAULT_ROUGHNESS = 0.9; -const float DEFAULT_SHININESS = 10.0; -const float DEFAULT_METALLIC = 0.0; -const vec3 DEFAULT_SPECULAR = vec3(0.1); -const vec3 DEFAULT_EMISSIVE = vec3(0.0); -const float DEFAULT_OCCLUSION = 1.0; -const float DEFAULT_SCATTERING = 0.0; -const vec3 DEFAULT_FRESNEL = DEFAULT_EMISSIVE; +<@include DefaultMaterials.slh@> void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion, float scattering) { if (alpha != 1.0) { diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index fc35267ddc..956b6c4a58 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -532,7 +532,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, } } - auto& program = deferredLightingEffect->_directionalSkyboxLight; + auto program = deferredLightingEffect->_directionalSkyboxLight; LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; auto keyLight = lightAndShadow.first; diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 5fccbd6a99..6b0e1cd642 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -122,17 +122,12 @@ float evalZeyeFromZdb(float depth) { return frameTransform._depthInfo.x / (depth * frameTransform._depthInfo.y + frameTransform._depthInfo.z); } -vec3 evalEyeNormal(vec3 C) { - //return normalize(cross(dFdy(C), dFdx(C))); - return normalize(cross(dFdx(C), dFdy(C))); +float evalZdbFromZeye(float Zeye) { + return (frameTransform._depthInfo.x - Zeye * frameTransform._depthInfo.z) / (Zeye * frameTransform._depthInfo.y); } -vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { - // compute the view space position using the depth - // basically manually pick the proj matrix components to do the inverse - float Xe = (-Zeye * (texcoord.x * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][0] - frameTransform._projection[side][3][0]) / frameTransform._projection[side][0][0]; - float Ye = (-Zeye * (texcoord.y * 2.0 - 1.0) - Zeye * frameTransform._projection[side][2][1] - frameTransform._projection[side][3][1]) / frameTransform._projection[side][1][1]; - return vec3(Xe, Ye, Zeye); +vec3 evalEyeNormal(vec3 C) { + return normalize(cross(dFdx(C), dFdy(C))); } vec3 evalEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { @@ -143,6 +138,11 @@ vec3 evalEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { return eyePos.xyz / eyePos.w; } +vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { + float Zdb = evalZdbFromZeye(Zeye); + return evalEyePositionFromZdb(side, Zdb, texcoord); +} + ivec2 getPixelPosTexcoordPosAndSide(in vec2 glFragCoord, out ivec2 pixelPos, out vec2 texcoordPos, out ivec4 stereoSide) { ivec2 fragPos = ivec2(glFragCoord.xy); diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index 78569b2837..e6337d7099 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -142,15 +142,18 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu // Mask out haze on the tablet PrepareStencil::testMask(*state); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HazeEffect_ParamsSlot)); - 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("keyLightBuffer"), HazeEffect_LightingMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - _hazePipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + gpu::doInBatch("DrawHaze::build", args->_context, [program](gpu::Batch& batch) { + batch.runLambda([program]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HazeEffect_ParamsSlot)); + 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("keyLightBuffer"), HazeEffect_LightingMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + }); } auto sourceFramebufferSize = glm::ivec2(inputBuffer->getDimensions()); diff --git a/libraries/render-utils/src/FadeEffectJobs.cpp b/libraries/render-utils/src/FadeEffectJobs.cpp index da3f8dddc0..1e2ea2bc15 100644 --- a/libraries/render-utils/src/FadeEffectJobs.cpp +++ b/libraries/render-utils/src/FadeEffectJobs.cpp @@ -39,43 +39,52 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F auto scene = renderContext->_scene; if (_isEditEnabled) { - float minIsectDistance = std::numeric_limits::max(); - auto& itemBounds = inputs.get0(); - auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance); - render::Transaction transaction; - bool hasTransaction{ false }; + static const std::string selectionName("TransitionEdit"); + auto scene = renderContext->_scene; + if (!scene->isSelectionEmpty(selectionName)) { + auto selection = scene->getSelection(selectionName); + auto editedItem = selection.getItems().front(); + render::Transaction transaction; + bool hasTransaction{ false }; - if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) { - // Remove transition from previously edited item as we've changed edited item - hasTransaction = true; + if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) { + // Remove transition from previously edited item as we've changed edited item + hasTransaction = true; + transaction.removeTransitionFromItem(_editedItem); + } + _editedItem = editedItem; + + if (render::Item::isValidID(_editedItem)) { + static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = { + render::Transition::ELEMENT_ENTER_DOMAIN, + render::Transition::BUBBLE_ISECT_OWNER, + render::Transition::BUBBLE_ISECT_TRESPASSER, + render::Transition::USER_ENTER_DOMAIN, + render::Transition::AVATAR_CHANGE + }; + + auto transitionType = categoryToTransition[inputs.get1()]; + + transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) { + if (transition == nullptr || transition->isFinished || transition->eventType != transitionType) { + // Relaunch transition + render::Transaction transaction; + transaction.addTransitionToItem(id, transitionType); + scene->enqueueTransaction(transaction); + } + }); + hasTransaction = true; + } + + if (hasTransaction) { + scene->enqueueTransaction(transaction); + } + } else if (render::Item::isValidID(_editedItem)) { + // Remove transition from previously edited item as we've disabled fade edition + render::Transaction transaction; transaction.removeTransitionFromItem(_editedItem); - } - _editedItem = editedItem; - - if (render::Item::isValidID(_editedItem)) { - static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = { - render::Transition::ELEMENT_ENTER_DOMAIN, - render::Transition::BUBBLE_ISECT_OWNER, - render::Transition::BUBBLE_ISECT_TRESPASSER, - render::Transition::USER_ENTER_DOMAIN, - render::Transition::AVATAR_CHANGE - }; - - auto transitionType = categoryToTransition[inputs.get1()]; - - transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) { - if (transition == nullptr || transition->isFinished || transition->eventType!=transitionType) { - // Relaunch transition - render::Transaction transaction; - transaction.addTransitionToItem(id, transitionType); - scene->enqueueTransaction(transaction); - } - }); - hasTransaction = true; - } - - if (hasTransaction) { scene->enqueueTransaction(transaction); + _editedItem = render::Item::INVALID_ITEM_ID; } } else if (render::Item::isValidID(_editedItem)) { @@ -87,28 +96,6 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F } } -render::ItemID FadeEditJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const { - const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition(); - const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection(); - BoxFace face; - glm::vec3 normal; - float isectDistance; - render::ItemID nearestItem = render::Item::INVALID_ITEM_ID; - const float minDistance = 1.f; - const float maxDistance = 50.f; - - for (const auto& itemBound : inputs) { - if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) { - auto& item = renderContext->_scene->getItem(itemBound.id); - if (item.getKey().isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistancemanualFade && (state.threshold != jobConfig->threshold)) { + if (isFirstItem && (state.threshold != jobConfig->threshold)) { jobConfig->setProperty("threshold", state.threshold); isFirstItem = false; } diff --git a/libraries/render-utils/src/FadeEffectJobs.h b/libraries/render-utils/src/FadeEffectJobs.h index 783f026bcd..449995dba5 100644 --- a/libraries/render-utils/src/FadeEffectJobs.h +++ b/libraries/render-utils/src/FadeEffectJobs.h @@ -160,8 +160,8 @@ public: float manualThreshold{ 0.f }; bool manualFade{ false }; - Q_INVOKABLE void save() const; - Q_INVOKABLE void load(); + Q_INVOKABLE void save(const QString& filePath) const; + Q_INVOKABLE void load(const QString& filePath); static QString eventNames[FADE_CATEGORY_COUNT]; @@ -190,7 +190,6 @@ private: bool _isEditEnabled{ false }; render::ItemID _editedItem{ render::Item::INVALID_ITEM_ID }; - render::ItemID findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const; }; class FadeJob { diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 3fd18a0eaf..ecfa9881a3 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -40,6 +40,7 @@ #include "simple_vert.h" #include "simple_textured_frag.h" +#include "simple_transparent_textured_frag.h" #include "simple_textured_unlit_frag.h" #include "simple_fade_vert.h" #include "simple_textured_fade_frag.h" @@ -49,6 +50,19 @@ #include "glowLine_vert.h" #include "glowLine_frag.h" +#include "forward_simple_textured_frag.h" +#include "forward_simple_textured_transparent_frag.h" +#include "forward_simple_textured_unlit_frag.h" + +#include "DeferredLightingEffect.h" + +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif + #include "grid_frag.h" //#define WANT_DEBUG @@ -679,6 +693,7 @@ gpu::Stream::FormatPointer& getInstancedSolidFadeStreamFormat() { QHash GeometryCache::_simplePrograms; gpu::ShaderPointer GeometryCache::_simpleShader; +gpu::ShaderPointer GeometryCache::_transparentShader; gpu::ShaderPointer GeometryCache::_unlitShader; gpu::ShaderPointer GeometryCache::_simpleFadeShader; gpu::ShaderPointer GeometryCache::_unlitFadeShader; @@ -774,8 +789,12 @@ render::ShapePipelinePointer GeometryCache::getShapePipeline(bool textured, bool bool unlit, bool depthBias) { return std::make_shared(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false, true), nullptr, - [](const render::ShapePipeline& , gpu::Batch& batch, render::Args*) { + [](const render::ShapePipeline& pipeline, gpu::Batch& batch, render::Args* args) { batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); + DependencyManager::get()->setupKeyLightBatch(args, batch, + pipeline.pipeline->getProgram()->getUniformBuffers().findLocation("keyLightBuffer"), + pipeline.pipeline->getProgram()->getUniformBuffers().findLocation("lightAmbientBuffer"), + pipeline.pipeline->getProgram()->getUniformBuffers().findLocation("skyboxMap")); } ); } @@ -798,7 +817,7 @@ render::ShapePipelinePointer GeometryCache::getOpaqueShapePipeline(bool isFading return isFading ? _simpleOpaqueFadePipeline : _simpleOpaquePipeline; } -render::ShapePipelinePointer GeometryCache::getTransparentShapePipeline(bool isFading) { +render::ShapePipelinePointer GeometryCache::getTransparentShapePipeline(bool isFading) { return isFading ? _simpleTransparentFadePipeline : _simpleTransparentPipeline; } @@ -843,7 +862,7 @@ void GeometryCache::renderWireShapeInstances(gpu::Batch& batch, Shape shape, siz _shapes[shape].drawWireInstances(batch, count); } -void setupBatchFadeInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer, +void setupBatchFadeInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer, gpu::BufferPointer fadeBuffer1, gpu::BufferPointer fadeBuffer2, gpu::BufferPointer fadeBuffer3) { gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); gpu::BufferView texCoord2View(fadeBuffer1, TEXCOORD4_ELEMENT); @@ -2003,7 +2022,7 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const auto program = gpu::Shader::createProgram(VS, PS); state->setCullMode(gpu::State::CULL_NONE); state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setBlendFunction(true, + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -2231,29 +2250,41 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp static std::once_flag once; std::call_once(once, [&]() { auto VS = simple_vert::getShader(); - auto PS = simple_textured_frag::getShader(); - auto PSUnlit = simple_textured_unlit_frag::getShader(); + auto PS = DISABLE_DEFERRED ? forward_simple_textured_frag::getShader() : simple_textured_frag::getShader(); + // Use the forward pipeline for both here, otherwise transparents will be unlit + auto PSTransparent = DISABLE_DEFERRED ? forward_simple_textured_transparent_frag::getShader() : forward_simple_textured_transparent_frag::getShader(); + auto PSUnlit = DISABLE_DEFERRED ? forward_simple_textured_unlit_frag::getShader() : simple_textured_unlit_frag::getShader(); _simpleShader = gpu::Shader::createProgram(VS, PS); + _transparentShader = gpu::Shader::createProgram(VS, PSTransparent); _unlitShader = gpu::Shader::createProgram(VS, PSUnlit); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), render::ShapePipeline::Slot::MAP::ALBEDO)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), render::ShapePipeline::Slot::LIGHTING_MODEL)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), render::ShapePipeline::Slot::KEY_LIGHT)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER)); + slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), render::ShapePipeline::Slot::MAP::LIGHT_AMBIENT)); gpu::Shader::makeProgram(*_simpleShader, slotBindings); + gpu::Shader::makeProgram(*_transparentShader, slotBindings); gpu::Shader::makeProgram(*_unlitShader, slotBindings); }); } else { static std::once_flag once; std::call_once(once, [&]() { auto VS = simple_fade_vert::getShader(); - auto PS = simple_textured_fade_frag::getShader(); - auto PSUnlit = simple_textured_unlit_fade_frag::getShader(); + auto PS = DISABLE_DEFERRED ? forward_simple_textured_frag::getShader() : simple_textured_fade_frag::getShader(); + auto PSUnlit = DISABLE_DEFERRED ? forward_simple_textured_unlit_frag::getShader() : simple_textured_unlit_fade_frag::getShader(); _simpleFadeShader = gpu::Shader::createProgram(VS, PS); _unlitFadeShader = gpu::Shader::createProgram(VS, PSUnlit); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), render::ShapePipeline::Slot::MAP::ALBEDO)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), render::ShapePipeline::Slot::LIGHTING_MODEL)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), render::ShapePipeline::Slot::KEY_LIGHT)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER)); + slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), render::ShapePipeline::Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), render::ShapePipeline::Slot::MAP::FADE_MASK)); gpu::Shader::makeProgram(*_simpleFadeShader, slotBindings); gpu::Shader::makeProgram(*_unlitFadeShader, slotBindings); @@ -2282,7 +2313,8 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp PrepareStencil::testMaskDrawShapeNoAA(*state); } - gpu::ShaderPointer program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : (config.isFading() ? _simpleFadeShader : _simpleShader); + gpu::ShaderPointer program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : + (config.isFading() ? _simpleFadeShader : (config.isTransparent() ? _transparentShader : _simpleShader)); gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); _simplePrograms.insert(config, pipeline); return pipeline; @@ -2363,11 +2395,11 @@ void renderFadeInstances(RenderArgs* args, gpu::Batch& batch, const glm::vec4& c pipeline->prepare(batch, args); if (isWire) { - DependencyManager::get()->renderWireFadeShapeInstances(batch, shape, data.count(), + DependencyManager::get()->renderWireFadeShapeInstances(batch, shape, data.count(), buffers[INSTANCE_COLOR_BUFFER], buffers[INSTANCE_FADE_BUFFER1], buffers[INSTANCE_FADE_BUFFER2], buffers[INSTANCE_FADE_BUFFER3]); } else { - DependencyManager::get()->renderFadeShapeInstances(batch, shape, data.count(), + DependencyManager::get()->renderFadeShapeInstances(batch, shape, data.count(), buffers[INSTANCE_COLOR_BUFFER], buffers[INSTANCE_FADE_BUFFER1], buffers[INSTANCE_FADE_BUFFER2], buffers[INSTANCE_FADE_BUFFER3]); } }); @@ -2383,15 +2415,15 @@ void GeometryCache::renderWireShapeInstance(RenderArgs* args, gpu::Batch& batch, renderInstances(args, batch, color, true, pipeline, shape); } -void GeometryCache::renderSolidFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, - int fadeCategory, float fadeThreshold, const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, +void GeometryCache::renderSolidFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, + int fadeCategory, float fadeThreshold, const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, const render::ShapePipelinePointer& pipeline) { assert(pipeline != nullptr); renderFadeInstances(args, batch, color, fadeCategory, fadeThreshold, fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, false, pipeline, shape); } -void GeometryCache::renderWireFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, - int fadeCategory, float fadeThreshold, const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, +void GeometryCache::renderWireFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, + int fadeCategory, float fadeThreshold, const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, const render::ShapePipelinePointer& pipeline) { assert(pipeline != nullptr); renderFadeInstances(args, batch, color, fadeCategory, fadeThreshold, fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, true, pipeline, shape); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index e0ba99b09e..fcbf5ee128 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -471,6 +471,7 @@ private: QHash _registeredGridBuffers; static gpu::ShaderPointer _simpleShader; + static gpu::ShaderPointer _transparentShader; static gpu::ShaderPointer _unlitShader; static gpu::ShaderPointer _simpleFadeShader; static gpu::ShaderPointer _unlitFadeShader; @@ -478,8 +479,6 @@ private: static render::ShapePipelinePointer _simpleTransparentPipeline; static render::ShapePipelinePointer _simpleOpaqueFadePipeline; static render::ShapePipelinePointer _simpleTransparentFadePipeline; - static render::ShapePipelinePointer _simpleOpaqueOverlayPipeline; - static render::ShapePipelinePointer _simpleTransparentOverlayPipeline; static render::ShapePipelinePointer _simpleWirePipeline; gpu::PipelinePointer _glowLinePipeline; diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index 008f29da01..b2377d1904 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -111,15 +111,11 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie } <@endif@> - if (!(isObscuranceEnabled() > 0.0)) { - obscurance = 1.0; - } + obscurance = mix(1.0, obscurance, isObscuranceEnabled()); float lightEnergy = obscurance * getLightAmbientIntensity(ambient); - if (isAlbedoEnabled() > 0.0) { - diffuse *= albedo; - } + diffuse *= mix(vec3(1), albedo, isAlbedoEnabled()); lightEnergy *= isAmbientEnabled(); diffuse *= lightEnergy * isDiffuseEnabled(); diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index 2280d12465..3f615f11db 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -13,11 +13,19 @@ <@func declareLightingModel()@> +#ifndef PRECISIONQ +#ifdef GL_ES +#define PRECISIONQ highp +#else +#define PRECISIONQ +#endif +#endif + struct LightingModel { - vec4 _UnlitEmissiveLightmapBackground; - vec4 _ScatteringDiffuseSpecularAlbedo; - vec4 _AmbientDirectionalPointSpot; - vec4 _ShowContourObscuranceWireframe; + PRECISIONQ vec4 _UnlitEmissiveLightmapBackground; + PRECISIONQ vec4 _ScatteringDiffuseSpecularAlbedo; + PRECISIONQ vec4 _AmbientDirectionalPointSpot; + PRECISIONQ vec4 _ShowContourObscuranceWireframe; }; uniform lightingModelBuffer{ @@ -256,9 +264,7 @@ void evalFragShading(out vec3 diffuse, out vec3 specular, float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { vec4 shading = evalPBRShading(metallic, fresnel, surface); diffuse = vec3(shading.w); - if (isAlbedoEnabled() > 0.0) { - diffuse *= albedo; - } + diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); specular = shading.xyz; } diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 26bb85a853..26e809e4ba 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -48,6 +48,74 @@ TexMapArray getTexMapArray() { <@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@> +<@include gpu/TextureTable.slh@> + +#ifdef GPU_TEXTURE_TABLE_BINDLESS + +TextureTable(0, matTex); + + +<@if withAlbedo@> +#define albedoMap 0 +vec4 fetchAlbedoMap(vec2 uv) { + return tableTexValue(matTex, albedoMap, uv); +} +<@endif@> + +<@if withRoughness@> +#define roughnessMap 4 +float fetchRoughnessMap(vec2 uv) { + return tableTexValue(matTex, roughnessMap, uv).r; +} +<@endif@> + +<@if withNormal@> +#define normalMap 1 +vec3 fetchNormalMap(vec2 uv) { + return tableTexValue(matTex, normalMap, uv).xyz; +} +<@endif@> + +<@if withMetallic@> +#define metallicMap 2 +float fetchMetallicMap(vec2 uv) { + return tableTexValue(matTex, metallicMap, uv).r; +} +<@endif@> + +<@if withEmissive@> +#define emissiveMap 3 +vec3 fetchEmissiveMap(vec2 uv) { + return tableTexValue(matTex, emissiveMap, uv).rgb; +} +<@endif@> + +<@if withOcclusion@> +#define occlusionMap 5 +float fetchOcclusionMap(vec2 uv) { + return tableTexValue(matTex, occlusionMap, uv).r; +} +<@endif@> + +<@if withScattering@> +#define scatteringMap 6 +float fetchScatteringMap(vec2 uv) { + float scattering = texture(tableTex(matTex, scatteringMap), uv).r; // boolean scattering for now + return max(((scattering - 0.1) / 0.9), 0.0); + return tableTexValue(matTex, scatteringMap, uv).r; // boolean scattering for now +} +<@endif@> + +#else + <@if withAlbedo@> uniform sampler2D albedoMap; vec4 fetchAlbedoMap(vec2 uv) { @@ -102,6 +170,8 @@ float fetchScatteringMap(vec2 uv) { } <@endif@> +#endif + <@endfunc@> diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 5c64d4b584..72a4b7e0a4 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -152,7 +152,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputStream(0, _drawMesh->getVertexStream()); } -void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { + void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { batch.setModelTransform(_drawTransform); } diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index aca8439547..a3abb24afe 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -32,6 +32,7 @@ #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" #include "model_translucent_vert.h" +#include "model_translucent_normal_map_vert.h" #include "skin_model_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" #include "skin_model_fade_dq_vert.h" @@ -68,11 +69,13 @@ #include "model_lightmap_normal_map_frag.h" #include "model_translucent_frag.h" #include "model_translucent_unlit_frag.h" +#include "model_translucent_normal_map_frag.h" #include "model_lightmap_fade_frag.h" #include "model_lightmap_normal_map_fade_frag.h" #include "model_translucent_fade_frag.h" #include "model_translucent_unlit_fade_frag.h" +#include "model_translucent_normal_map_fade_frag.h" #include "overlay3D_vert.h" #include "overlay3D_frag.h" @@ -187,6 +190,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelLightmapVertex = model_lightmap_vert::getShader(); auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader(); auto modelTranslucentVertex = model_translucent_vert::getShader(); + auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader(); auto modelShadowVertex = model_shadow_vert::getShader(); auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); @@ -227,6 +231,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelNormalMapPixel = model_normal_map_frag::getShader(); auto modelTranslucentPixel = model_translucent_frag::getShader(); auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader(); + auto modelTranslucentNormalMapPixel = model_translucent_normal_map_frag::getShader(); auto modelShadowPixel = model_shadow_frag::getShader(); auto modelLightmapPixel = model_lightmap_frag::getShader(); auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader(); @@ -239,6 +244,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelShadowFadePixel = model_shadow_fade_frag::getShader(); auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader(); auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader(); + auto modelTranslucentNormalMapFadePixel = model_translucent_normal_map_fade_frag::getShader(); auto simpleFadePixel = simple_textured_fade_frag::getShader(); auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader(); auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader(); @@ -296,7 +302,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentNormalMapVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), @@ -316,7 +322,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withFade(), - modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + modelTranslucentNormalMapVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(), @@ -358,14 +364,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(), skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); // dual quaternion skinned addPipeline( @@ -388,14 +394,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(), skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); // Depth-only addPipeline( @@ -612,7 +618,8 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { #include "RenderPipelines.h" #include -void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) { +// FIXME find a better way to setup the default textures +void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) { if (!material) { return; } @@ -630,84 +637,65 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc numUnlit++; } - if (!enableTextures) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); - return; - } + const auto& drawMaterialTextures = material->getTextureTable(); // Albedo if (materialKey.isAlbedoMap()) { auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); } } // Roughness map if (materialKey.isRoughnessMap()) { auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, textureCache->getWhiteTexture()); } } // Normal map if (materialKey.isNormalMap()) { auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, textureCache->getBlueTexture()); } } // Metallic map if (materialKey.isMetallicMap()) { auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, textureCache->getBlackTexture()); } } // Occlusion map if (materialKey.isOcclusionMap()) { auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, textureCache->getWhiteTexture()); } } // Scattering map if (materialKey.isScatteringMap()) { auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, textureCache->getWhiteTexture()); } } @@ -715,18 +703,19 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc if (materialKey.isLightmapMap()) { auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); } } else if (materialKey.isEmissiveMap()) { auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); - - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); } } + + batch.setResourceTextureTable(material->getTextureTable()); } diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h index 9b9ad2c001..b7d22bc72d 100644 --- a/libraries/render-utils/src/RenderPipelines.h +++ b/libraries/render-utils/src/RenderPipelines.h @@ -15,7 +15,7 @@ class RenderPipelines { public: - static void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures); + static void bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures); }; diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index 0d49fc037e..bc8063e018 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -1,10 +1,8 @@ // glsl / C++ compatible source as interface for Shadows #ifdef __cplusplus # define MAT4 glm::mat4 -# define VEC3 glm::vec3 #else # define MAT4 mat4 -# define VEC3 vec3 #endif #define SHADOW_CASCADE_MAX_COUNT 4 diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 50067d003f..1ffa0d2e5c 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -311,9 +311,6 @@ void diffuseProfileGPU(gpu::TexturePointer& profileMap, RenderArgs* args) { auto ps = subsurfaceScattering_makeProfile_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); makePipeline = gpu::Pipeline::create(program, state); @@ -342,26 +339,27 @@ void diffuseScatterGPU(const gpu::TexturePointer& profileMap, gpu::TexturePointe int height = lut->getHeight(); gpu::PipelinePointer makePipeline; - { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = subsurfaceScattering_makeLUT_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = subsurfaceScattering_makeLUT_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringProfile"), 0)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - - makePipeline = gpu::Pipeline::create(program, state); - } + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + makePipeline = gpu::Pipeline::create(program, state); + auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("diffuseScatter")); makeFramebuffer->setRenderBuffer(0, lut); gpu::doInBatch("SubsurfaceScattering::diffuseScatterGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); + batch.runLambda([program] (){ + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("scatteringProfile"), 0)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + batch.setViewportTransform(glm::ivec4(0, 0, width, height)); batch.setFramebuffer(makeFramebuffer); @@ -385,9 +383,6 @@ void computeSpecularBeckmannGPU(gpu::TexturePointer& beckmannMap, RenderArgs* ar auto ps = subsurfaceScattering_makeSpecularBeckmann_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); makePipeline = gpu::Pipeline::create(program, state); @@ -495,7 +490,6 @@ gpu::PipelinePointer DebugSubsurfaceScattering::getShowLUTPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index cfdb67ecb6..d3bf3ab198 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -167,7 +167,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con outputs.edit3() = halfLinearDepthTexture; outputs.edit4() = halfNormalTexture; - auto linearDepthPipeline = getLinearDepthPipeline(); + auto linearDepthPipeline = getLinearDepthPipeline(renderContext); auto downsamplePipeline = getDownsamplePipeline(); auto depthViewport = args->_viewport; @@ -209,17 +209,12 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con } -const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { +const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline(const render::RenderContextPointer& renderContext) { + gpu::ShaderPointer program; if (!_linearDepthPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = surfaceGeometry_makeLinearDepth_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DepthLinearPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DepthLinearPass_DepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + program = gpu::Shader::createProgram(vs, ps); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -230,8 +225,18 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { // Good to go add the brand new pipeline _linearDepthPipeline = gpu::Pipeline::create(program, state); + + gpu::doInBatch("LinearDepthPass::run", renderContext->args->_context, [program](gpu::Batch& batch) { + batch.runLambda([program]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DepthLinearPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DepthLinearPass_DepthMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + }); } + return _linearDepthPipeline; } @@ -455,7 +460,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, outputs.edit2() = curvatureFramebuffer; outputs.edit3() = lowCurvatureFramebuffer; - auto curvaturePipeline = getCurvaturePipeline(); + auto curvaturePipeline = getCurvaturePipeline(renderContext); auto diffuseVPipeline = _diffusePass.getBlurVPipeline(); auto diffuseHPipeline = _diffusePass.getBlurHPipeline(); @@ -536,21 +541,12 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, config->setGPUBatchRunTime(_gpuTimer->getGPUAverage(), _gpuTimer->getBatchAverage()); } - -const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { +const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline(const render::RenderContextPointer& renderContext) { if (!_curvaturePipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = surfaceGeometry_makeCurvature_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), SurfaceGeometryPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("surfaceGeometryParamsBuffer"), SurfaceGeometryPass_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), SurfaceGeometryPass_DepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), SurfaceGeometryPass_NormalMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); #ifdef USE_STENCIL_TEST @@ -559,6 +555,17 @@ const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { #endif // Good to go add the brand new pipeline _curvaturePipeline = gpu::Pipeline::create(program, state); + + gpu::doInBatch("SurfaceGeometryPass::CurvaturePipeline", renderContext->args->_context, [program](gpu::Batch& batch) { + batch.runLambda([program]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), SurfaceGeometryPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("surfaceGeometryParamsBuffer"), SurfaceGeometryPass_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), SurfaceGeometryPass_DepthMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), SurfaceGeometryPass_NormalMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + }); } return _curvaturePipeline; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.h b/libraries/render-utils/src/SurfaceGeometryPass.h index 859bcaa07a..501cf3fa87 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.h +++ b/libraries/render-utils/src/SurfaceGeometryPass.h @@ -81,7 +81,7 @@ private: LinearDepthFramebufferPointer _linearDepthFramebuffer; - const gpu::PipelinePointer& getLinearDepthPipeline(); + const gpu::PipelinePointer& getLinearDepthPipeline(const render::RenderContextPointer& renderContext); gpu::PipelinePointer _linearDepthPipeline; const gpu::PipelinePointer& getDownsamplePipeline(); @@ -195,7 +195,7 @@ private: SurfaceGeometryFramebufferPointer _surfaceGeometryFramebuffer; - const gpu::PipelinePointer& getCurvaturePipeline(); + const gpu::PipelinePointer& getCurvaturePipeline(const render::RenderContextPointer& renderContext); gpu::PipelinePointer _curvaturePipeline; diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 6857de62a7..12152fd6ba 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -27,19 +27,24 @@ ToneMappingEffect::ToneMappingEffect() { _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); } -void ToneMappingEffect::init() { +void ToneMappingEffect::init(RenderArgs* args) { auto blitPS = toneMapping_frag::getShader(); auto blitVS = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto blitProgram = gpu::ShaderPointer(gpu::Shader::createProgram(blitVS, blitPS)); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("toneMappingParamsBuffer"), ToneMappingEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), ToneMappingEffect_LightingMapSlot)); - gpu::Shader::makeProgram(*blitProgram, slotBindings); auto blitState = std::make_shared(); blitState->setColorWriteMask(true, true, true, true); _blitLightBuffer = gpu::PipelinePointer(gpu::Pipeline::create(blitProgram, blitState)); + + gpu::doInBatch("ToneMappingEffect::toneMapping", args->_context, [blitProgram](gpu::Batch& batch) { + batch.runLambda([blitProgram]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("toneMappingParamsBuffer"), ToneMappingEffect_ParamsSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), ToneMappingEffect_LightingMapSlot)); + gpu::Shader::makeProgram(*blitProgram, slotBindings); + }); + }); } void ToneMappingEffect::setExposure(float exposure) { @@ -59,7 +64,7 @@ void ToneMappingEffect::setToneCurve(ToneCurve curve) { void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& requestedDestinationFramebuffer) { if (!_blitLightBuffer) { - init(); + init(args); } auto destinationFramebuffer = requestedDestinationFramebuffer; diff --git a/libraries/render-utils/src/ToneMappingEffect.h b/libraries/render-utils/src/ToneMappingEffect.h index 2d1414e1ef..046e7606b3 100644 --- a/libraries/render-utils/src/ToneMappingEffect.h +++ b/libraries/render-utils/src/ToneMappingEffect.h @@ -59,7 +59,7 @@ private: typedef gpu::BufferView UniformBufferView; gpu::BufferView _parametersBuffer; - void init(); + void init(RenderArgs* args); }; class ToneMappingConfig : public render::Job::Config { diff --git a/libraries/render-utils/src/forward_simple.slf b/libraries/render-utils/src/forward_simple.slf new file mode 100644 index 0000000000..ac2aa63697 --- /dev/null +++ b/libraries/render-utils/src/forward_simple.slf @@ -0,0 +1,83 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// forward_simple.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 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 DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> + +// the interpolated normal +in vec3 _normal; +in vec3 _modelNormal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _position; +in vec4 _eyePosition; + +layout(location = 0) out vec4 _fragColor0; + +//PROCEDURAL_COMMON_BLOCK + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2030 +void main(void) { + vec3 normal = normalize(_normal.xyz); + vec3 diffuse = _color.rgb; + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; + float emissiveAmount = 0.0; + +#ifdef PROCEDURAL + +#ifdef PROCEDURAL_V1 + specular = getProceduralColor().rgb; + // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline + //specular = pow(specular, vec3(2.2)); + emissiveAmount = 1.0; +#else + emissiveAmount = getProceduralColors(diffuse, specular, shininess); +#endif + +#endif + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _eyePosition.xyz; + + if (emissiveAmount > 0.0) { + _fragColor0 = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normal, + diffuse, + specular, + DEFAULT_METALLIC, + max(0.0, 1.0 - shininess / 128.0)), + 1.0); + } else { + _fragColor0 = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normal, + diffuse, + DEFAULT_FRESNEL, + length(specular), + max(0.0, 1.0 - shininess / 128.0)), + 1.0); + } +} diff --git a/libraries/render-utils/src/forward_simple_textured.slf b/libraries/render-utils/src/forward_simple_textured.slf new file mode 100644 index 0000000000..c88482c208 --- /dev/null +++ b/libraries/render-utils/src/forward_simple_textured.slf @@ -0,0 +1,51 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// forward_simple_textured.slf +// fragment shader +// +// Created by Clément Brisset on 5/29/15. +// Copyright 2014 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 DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec3 _normal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _eyePosition; + +layout(location = 0) out vec4 _fragColor0; + +void main(void) { + vec4 texel = texture(originalTexture, _texCoord0); + float colorAlpha = _color.a * texel.a; + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _eyePosition.xyz; + + _fragColor0 = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normalize(_normal), + _color.rgb * texel.rgb, + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_ROUGHNESS), + 1.0); +} \ No newline at end of file diff --git a/libraries/render-utils/src/forward_simple_textured_transparent.slf b/libraries/render-utils/src/forward_simple_textured_transparent.slf new file mode 100644 index 0000000000..a8f23907d3 --- /dev/null +++ b/libraries/render-utils/src/forward_simple_textured_transparent.slf @@ -0,0 +1,52 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// forward_simple_textured_transparent.slf +// fragment shader +// +// Created by Clément Brisset on 5/29/15. +// Copyright 2014 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 DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlended()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec3 _normal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _eyePosition; + +layout(location = 0) out vec4 _fragColor0; + +void main(void) { + vec4 texel = texture(originalTexture, _texCoord0); + float colorAlpha = _color.a * texel.a; + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _eyePosition.xyz; + + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normalize(_normal), + _color.rgb * texel.rgb, + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_ROUGHNESS, colorAlpha), + colorAlpha); +} \ No newline at end of file diff --git a/libraries/render-utils/src/forward_simple_textured_unlit.slf b/libraries/render-utils/src/forward_simple_textured_unlit.slf new file mode 100644 index 0000000000..0cc241b75e --- /dev/null +++ b/libraries/render-utils/src/forward_simple_textured_unlit.slf @@ -0,0 +1,31 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_textured_unlit.frag +// fragment shader +// +// Created by Clément Brisset on 5/29/15. +// Copyright 2014 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 LightingModel.slh@> +<@include gpu/Color.slh@> + +layout(location = 0) out vec4 _fragColor0; + +// the albedo texture +uniform sampler2D originalTexture; + +in vec4 _color; +in vec2 _texCoord0; + +void main(void) { + vec4 texel = texture(originalTexture, _texCoord0.st); + float colorAlpha = _color.a * texel.a; + + _fragColor0 = vec4(_color.rgb * texel.rgb * isUnlitEnabled(), colorAlpha); +} \ No newline at end of file diff --git a/libraries/render-utils/src/forward_simple_transparent.slf b/libraries/render-utils/src/forward_simple_transparent.slf new file mode 100644 index 0000000000..551e10282a --- /dev/null +++ b/libraries/render-utils/src/forward_simple_transparent.slf @@ -0,0 +1,84 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// forward_simple_transparent.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 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 DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlended()$> + +// the interpolated normal +in vec3 _normal; +in vec3 _modelNormal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _eyePosition; + +layout(location = 0) out vec4 _fragColor0; + +//PROCEDURAL_COMMON_BLOCK + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2030 +void main(void) { + vec3 normal = normalize(_normal.xyz); + vec3 diffuse = _color.rgb; + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; + float emissiveAmount = 0.0; + +#ifdef PROCEDURAL + +#ifdef PROCEDURAL_V1 + specular = getProceduralColor().rgb; + // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline + //specular = pow(specular, vec3(2.2)); + emissiveAmount = 1.0; +#else + emissiveAmount = getProceduralColors(diffuse, specular, shininess); +#endif + +#endif + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _eyePosition.xyz; + + if (emissiveAmount > 0.0) { + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normal, + specular, + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_ROUGHNESS, _color.a), + _color.a); + } else { + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normal, + diffuse, + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_ROUGHNESS, _color.a), + _color.a); + } +} diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index 739709418d..d4d97c5b16 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -32,7 +32,7 @@ void main(void) { // Grab the fragment data from the uv vec2 texCoord = varTexCoord0.st; - vec4 fragEyePos = unpackDeferredPositionFromZeye(texCoord); + vec4 fragEyePos = unpackDeferredPositionFromZdb(texCoord); vec4 fragWorldPos = getViewInverse() * fragEyePos; // From frag world pos find the cluster diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index ee2e6e0ccc..c51d45ed44 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -29,7 +29,7 @@ void main(void) { // Grab the fragment data from the uv vec2 texCoord = varTexCoord0.st; - vec4 fragEyePos = unpackDeferredPositionFromZeye(texCoord); + vec4 fragEyePos = unpackDeferredPositionFromZdb(texCoord); vec4 fragWorldPos = getViewInverse() * fragEyePos; // From frag world pos find the cluster diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 7b32541c63..1c2ad9a413 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -13,7 +13,6 @@ // <@include DeferredBufferWrite.slh@> -<@include graphics/Material.slh@> // the interpolated normal in vec3 _normal; @@ -29,9 +28,8 @@ in vec4 _position; #line 2030 void main(void) { - Material material = getMaterial(); - vec3 normal = normalize(_normal.xyz); - vec3 diffuse = _color.rgb; + vec3 normal = normalize(_normal.xyz); + vec3 diffuse = _color.rgb; vec3 specular = DEFAULT_SPECULAR; float shininess = DEFAULT_SHININESS; float emissiveAmount = 0.0; @@ -43,49 +41,30 @@ void main(void) { // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline //specular = pow(specular, vec3(2.2)); emissiveAmount = 1.0; -#else +#else emissiveAmount = getProceduralColors(diffuse, specular, shininess); #endif #endif - const float ALPHA_THRESHOLD = 0.999; - if (_color.a < ALPHA_THRESHOLD) { - if (emissiveAmount > 0.0) { - packDeferredFragmentTranslucent( - normal, - _color.a, - specular, - DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); - } else { - packDeferredFragmentTranslucent( - normal, - _color.a, - diffuse, - DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); - } + if (emissiveAmount > 0.0) { + packDeferredFragmentLightmap( + normal, + 1.0, + diffuse, + max(0.0, 1.0 - shininess / 128.0), + DEFAULT_METALLIC, + specular, + vec3(clamp(emissiveAmount, 0.0, 1.0))); } else { - if (emissiveAmount > 0.0) { - packDeferredFragmentLightmap( - normal, - 1.0, - diffuse, - max(0.0, 1.0 - shininess / 128.0), - DEFAULT_METALLIC, - specular, - vec3(clamp(emissiveAmount, 0.0, 1.0))); - } else { - packDeferredFragment( - normal, - 1.0, - diffuse, - max(0.0, 1.0 - shininess / 128.0), - length(specular), - DEFAULT_EMISSIVE, - DEFAULT_OCCLUSION, - DEFAULT_SCATTERING); - } + packDeferredFragment( + normal, + 1.0, + diffuse, + max(0.0, 1.0 - shininess / 128.0), + length(specular), + DEFAULT_EMISSIVE, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING); } } diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 0ce6505a65..f63ccb61a6 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -23,6 +23,7 @@ out vec3 _modelNormal; out vec4 _color; out vec2 _texCoord0; out vec4 _position; +out vec4 _eyePosition; void main(void) { _color = color_sRGBAToLinear(inColor); @@ -33,6 +34,6 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _eyePosition, gl_Position)$> <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf index ce9251b9a5..1c74612ff0 100644 --- a/libraries/render-utils/src/simple_fade.slf +++ b/libraries/render-utils/src/simple_fade.slf @@ -13,7 +13,6 @@ // <@include DeferredBufferWrite.slh@> -<@include graphics/Material.slh@> <@include Fade.slh@> <$declareFadeFragmentInstanced()$> @@ -39,7 +38,6 @@ void main(void) { <$fetchFadeObjectParamsInstanced(fadeParams)$> applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); - Material material = getMaterial(); vec3 normal = normalize(_normal.xyz); vec3 diffuse = _color.rgb; vec3 specular = DEFAULT_SPECULAR; diff --git a/libraries/render-utils/src/simple_fade.slv b/libraries/render-utils/src/simple_fade.slv index 85946045ac..6dbe40485a 100644 --- a/libraries/render-utils/src/simple_fade.slv +++ b/libraries/render-utils/src/simple_fade.slv @@ -26,6 +26,7 @@ out vec3 _modelNormal; out vec4 _color; out vec2 _texCoord0; out vec4 _position; +out vec4 _eyePosition; out vec4 _worldPosition; void main(void) { @@ -37,7 +38,7 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _eyePosition, gl_Position)$> <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> <$passThroughFadeObjectParams()$> diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 4fd734aad5..bb50c9d158 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -12,9 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Color.slh@> <@include DeferredBufferWrite.slh@> -<@include graphics/Material.slh@> // the albedo texture uniform sampler2D originalTexture; @@ -26,29 +24,14 @@ in vec2 _texCoord0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0); - float colorAlpha = _color.a; - if (_color.a <= 0.0) { - texel = color_sRGBAToLinear(texel); - colorAlpha = -_color.a; - } - const float ALPHA_THRESHOLD = 0.999; - if (colorAlpha * texel.a < ALPHA_THRESHOLD) { - packDeferredFragmentTranslucent( - normalize(_normal), - colorAlpha * texel.a, - _color.rgb * texel.rgb, - DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); - } else { - packDeferredFragment( - normalize(_normal), - 1.0, - _color.rgb * texel.rgb, - DEFAULT_ROUGHNESS, - DEFAULT_METALLIC, - DEFAULT_EMISSIVE, - DEFAULT_OCCLUSION, - DEFAULT_SCATTERING); - } + packDeferredFragment( + normalize(_normal), + 1.0, + _color.rgb * texel.rgb, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING); } \ No newline at end of file diff --git a/libraries/render-utils/src/simple_textured_fade.slf b/libraries/render-utils/src/simple_textured_fade.slf index d378e7a5c1..e5ecd94852 100644 --- a/libraries/render-utils/src/simple_textured_fade.slf +++ b/libraries/render-utils/src/simple_textured_fade.slf @@ -14,7 +14,6 @@ <@include gpu/Color.slh@> <@include DeferredBufferWrite.slh@> -<@include graphics/Material.slh@> <@include Fade.slh@> diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf new file mode 100644 index 0000000000..65ffe0504a --- /dev/null +++ b/libraries/render-utils/src/simple_transparent.slf @@ -0,0 +1,65 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_transparent.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/15/14. +// Copyright 2014 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 DeferredBufferWrite.slh@> + +// the interpolated normal +in vec3 _normal; +in vec3 _modelNormal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _position; + +//PROCEDURAL_COMMON_BLOCK + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2030 +void main(void) { + vec3 normal = normalize(_normal.xyz); + vec3 diffuse = _color.rgb; + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; + float emissiveAmount = 0.0; + +#ifdef PROCEDURAL + +#ifdef PROCEDURAL_V1 + specular = getProceduralColor().rgb; + // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline + //specular = pow(specular, vec3(2.2)); + emissiveAmount = 1.0; +#else + emissiveAmount = getProceduralColors(diffuse, specular, shininess); +#endif + +#endif + + if (emissiveAmount > 0.0) { + packDeferredFragmentTranslucent( + normal, + _color.a, + specular, + DEFAULT_FRESNEL, + DEFAULT_ROUGHNESS); + } else { + packDeferredFragmentTranslucent( + normal, + _color.a, + diffuse, + DEFAULT_FRESNEL, + DEFAULT_ROUGHNESS); + } +} diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index c6b0d83914..7c7168ec0f 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -12,51 +12,24 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Color.slh@> - <@include DeferredBufferWrite.slh@> -<@include DeferredGlobalLight.slh@> -<$declareEvalGlobalLightingAlphaBlendedWithHaze()$> - -<@include gpu/Transform.slh@> -<$declareStandardCameraTransform()$> // the albedo texture uniform sampler2D originalTexture; // the interpolated normal -in vec4 _position; in vec3 _normal; in vec4 _color; in vec2 _texCoord0; void main(void) { - vec4 texel = texture(originalTexture, _texCoord0.st); - float opacity = _color.a; - if (_color.a <= 0.0) { - texel = color_sRGBAToLinear(texel); - opacity = -_color.a; - } - opacity *= texel.a; - vec3 albedo = _color.rgb * texel.rgb; + vec4 texel = texture(originalTexture, _texCoord0); + float colorAlpha = _color.a * texel.a; - vec3 fragPosition = _position.xyz; - vec3 fragNormal = normalize(_normal); - - TransformCamera cam = getTransformCamera(); - - _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( - cam._viewInverse, - 1.0, - 1.0, - fragPosition, - fragNormal, - albedo, + packDeferredFragmentTranslucent( + normalize(_normal), + colorAlpha, + _color.rgb * texel.rgb, DEFAULT_FRESNEL, - 0.0, - vec3(0.0f), - DEFAULT_ROUGHNESS, - opacity), - opacity); - + DEFAULT_ROUGHNESS); } \ No newline at end of file diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index bdff97c1c1..8aabffea46 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -41,6 +41,11 @@ void render::renderItems(const RenderContextPointer& renderContext, const ItemBo } } +namespace { + int repeatedInvalidKeyMessageID = 0; + std::once_flag messageIDFlag; +} + void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, const Item& item, const ShapeKey& globalKey) { assert(item.getKey().isShape()); auto key = item.getShapeKey() | globalKey; @@ -55,9 +60,9 @@ void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, cons } else if (key.hasOwnPipeline()) { item.render(args); } else { - qCDebug(renderlogging) << "Item could not be rendered with invalid key" << key; - static QString repeatedCouldNotBeRendered = LogHandler::getInstance().addRepeatedMessageRegex( - "Item could not be rendered with invalid key.*"); + std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); }, + &repeatedInvalidKeyMessageID); + HIFI_FCDEBUG_ID(renderlogging(), repeatedInvalidKeyMessageID, "Item could not be rendered with invalid key" << key); } args->_itemShapeKey = 0; } @@ -108,9 +113,9 @@ void render::renderStateSortShapes(const RenderContextPointer& renderContext, } else if (key.hasOwnPipeline()) { ownPipelineBucket.push_back( std::make_tuple(item, key) ); } else { - static QString repeatedCouldNotBeRendered = LogHandler::getInstance().addRepeatedMessageRegex( - "Item could not be rendered with invalid key.*"); - qCDebug(renderlogging) << "Item could not be rendered with invalid key" << key; + std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); }, + &repeatedInvalidKeyMessageID); + HIFI_FCDEBUG_ID(renderlogging(), repeatedInvalidKeyMessageID, "Item could not be rendered with invalid key" << key); } } } diff --git a/libraries/render/src/render/Engine.cpp b/libraries/render/src/render/Engine.cpp index 463b45451b..b0842d63cd 100644 --- a/libraries/render/src/render/Engine.cpp +++ b/libraries/render/src/render/Engine.cpp @@ -36,7 +36,7 @@ public: } }; -Engine::Engine() : Task("Engine", EngineTask::JobModel::create()), +Engine::Engine() : Task(EngineTask::JobModel::create("Engine")), _renderContext(std::make_shared()) { } diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index ed052adf6e..9c5efb9fa7 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -118,9 +118,15 @@ uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) c auto numSubs = fetchMetaSubItems(subItems); for (auto id : subItems) { - auto& item = scene.getItem(id); - if (item.exist()) { - subItemBounds.emplace_back(id, item.getBound()); + // TODO: Adding an extra check here even thought we shouldn't have too. + // We have cases when the id returned by fetchMetaSubItems is not allocated + if (scene.isAllocatedID(id)) { + auto& item = scene.getItem(id); + if (item.exist()) { + subItemBounds.emplace_back(id, item.getBound()); + } else { + numSubs--; + } } else { numSubs--; } diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 92e22d86f6..35cc66315b 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -134,9 +134,12 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightClusterFrustumBufferUnit = -1; } - auto gpuPipeline = gpu::Pipeline::create(program, state); - auto shapePipeline = std::make_shared(gpuPipeline, locations, batchSetter, itemSetter); - addPipelineHelper(filter, key, 0, shapePipeline); + { + PROFILE_RANGE(app, "Pipeline::create"); + auto gpuPipeline = gpu::Pipeline::create(program, state); + auto shapePipeline = std::make_shared(gpuPipeline, locations, batchSetter, itemSetter); + addPipelineHelper(filter, key, 0, shapePipeline); + } } const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Key& key) const { diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 4942c63e27..930da6a494 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -59,5 +59,7 @@ static const float MIN_AVATAR_SCALE = 0.005f; static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters +static const float AVATAR_WALK_SPEED_SCALAR = 1.0f; +static const float AVATAR_SPRINT_SPEED_SCALAR = 3.0f; #endif // hifi_AvatarConstants_h diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 49927a325b..45cf01510d 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -25,7 +25,7 @@ #include #include -QMutex LogHandler::_mutex; +QMutex LogHandler::_mutex(QMutex::Recursive); LogHandler& LogHandler::getInstance() { static LogHandler staticInstance; @@ -36,6 +36,9 @@ LogHandler::LogHandler() { // when the log handler is first setup we should print our timezone QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t"); printMessage(LogMsgType::LogInfo, QMessageLogContext(), timezoneString); + + // make sure we setup the repeated message flusher, but do it on the LogHandler thread + QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher"); } LogHandler::~LogHandler() { @@ -91,58 +94,25 @@ void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { void LogHandler::flushRepeatedMessages() { QMutexLocker lock(&_mutex); - for(auto& message: _repeatedMessages) { - if (message.messageCount > 1) { - QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") - .arg(message.messageCount - 1) - .arg(message.regexp.pattern()) - .arg(message.lastMessage); - - QMessageLogContext emptyContext; - lock.unlock(); - printMessage(LogSuppressed, emptyContext, repeatMessage); - lock.relock(); + // New repeat-suppress scheme: + for (int m = 0; m < (int)_repeatedMessageRecords.size(); ++m) { + int repeatCount = _repeatedMessageRecords[m].repeatCount; + if (repeatCount > 1) { + QString repeatLogMessage = QString().setNum(repeatCount) + " repeated log entries - Last entry: \"" + + _repeatedMessageRecords[m].repeatString + "\""; + printMessage(LogSuppressed, QMessageLogContext(), repeatLogMessage); + _repeatedMessageRecords[m].repeatCount = 0; + _repeatedMessageRecords[m].repeatString = QString(); } - - message.messageCount = 0; } } QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) { - QMutexLocker lock(&_mutex); if (message.isEmpty()) { return QString(); } - - if (type == LogDebug) { - // for debug messages, check if this matches any of our regexes for repeated log messages - for (auto& repeatRegex : _repeatedMessages) { - if (repeatRegex.regexp.indexIn(message) != -1) { - // If we've printed the first one then return out. - if (repeatRegex.messageCount++ == 0) { - break; - } - repeatRegex.lastMessage = message; - return QString(); - } - } - } - - if (type == LogDebug) { - // see if this message is one we should only print once - for (auto& onceOnly : _onetimeMessages) { - if (onceOnly.regexp.indexIn(message) != -1) { - if (onceOnly.messageCount++ == 0) { - // we have a match and haven't yet printed this message. - break; - } else { - // We've already printed this message, don't print it again. - return QString(); - } - } - } - } + QMutexLocker lock(&_mutex); // log prefix is in the following format // [TIMESTAMP] [DEBUG] [PID] [TID] [TARGET] logged string @@ -199,21 +169,27 @@ void LogHandler::setupRepeatedMessageFlusher() { }); } -const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) { - // make sure we setup the repeated message flusher, but do it on the LogHandler thread - QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher"); - +int LogHandler::newRepeatedMessageID() { QMutexLocker lock(&_mutex); - RepeatedMessage repeatRecord; - repeatRecord.regexp = QRegExp(regexString); - _repeatedMessages.push_back(repeatRecord); - return regexString; + int newMessageId = _currentMessageID; + ++_currentMessageID; + RepeatedMessageRecord newRecord { 0, QString() }; + _repeatedMessageRecords.push_back(newRecord); + return newMessageId; } -const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) { +void LogHandler::printRepeatedMessage(int messageID, LogMsgType type, const QMessageLogContext& context, + const QString& message) { QMutexLocker lock(&_mutex); - OnceOnlyMessage onetimeMessage; - onetimeMessage.regexp = QRegExp(regexString); - _onetimeMessages.push_back(onetimeMessage); - return regexString; + if (messageID >= _currentMessageID) { + return; + } + + if (_repeatedMessageRecords[messageID].repeatCount == 0) { + printMessage(type, context, message); + } else { + _repeatedMessageRecords[messageID].repeatString = message; + } + + ++_repeatedMessageRecords[messageID].repeatCount; } diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index 2e64f16c1e..56450768ff 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -51,8 +51,8 @@ public: /// prints various process, message type, and time information static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); - const QString& addRepeatedMessageRegex(const QString& regexString); - const QString& addOnlyOnceMessageRegex(const QString& regexString); + int newRepeatedMessageID(); + void printRepeatedMessage(int messageID, LogMsgType type, const QMessageLogContext& context, const QString &message); private slots: void setupRepeatedMessageFlusher(); @@ -68,20 +68,40 @@ private: bool _shouldOutputThreadID { false }; bool _shouldDisplayMilliseconds { false }; - struct RepeatedMessage { - QRegExp regexp; - int messageCount { 0 }; - QString lastMessage; + int _currentMessageID { 0 }; + struct RepeatedMessageRecord { + int repeatCount; + QString repeatString; }; - std::vector _repeatedMessages; - - struct OnceOnlyMessage { - QRegExp regexp; - int messageCount { 0 }; - }; - std::vector _onetimeMessages; - + std::vector _repeatedMessageRecords; static QMutex _mutex; }; +#define HIFI_FCDEBUG(category, message) \ + do { \ + if (category.isDebugEnabled()) { \ + static int repeatedMessageID_ = LogHandler::getInstance().newRepeatedMessageID(); \ + QString logString_; \ + QDebug debugStringReceiver_(&logString_); \ + debugStringReceiver_ << message; \ + LogHandler::getInstance().printRepeatedMessage(repeatedMessageID_, LogDebug, QMessageLogContext(__FILE__, \ + __LINE__, __func__, category().categoryName()), logString_); \ + } \ + } while (false) + +#define HIFI_FDEBUG(message) HIFI_FCDEBUG((*QLoggingCategory::defaultCategory()), message) + +#define HIFI_FCDEBUG_ID(category, messageID, message) \ + do { \ + if (category.isDebugEnabled()) { \ + QString logString_; \ + QDebug debugStringReceiver_(&logString_); \ + debugStringReceiver_ << message; \ + LogHandler::getInstance().printRepeatedMessage(messageID, LogDebug, QMessageLogContext(__FILE__, \ + __LINE__, __func__, category().categoryName()), logString_); \ + } \ + } while (false) + +#define HIFI_FDEBUG_ID(messageID, message) HIFI_FCDEBUG_ID((*QLoggingCategory::defaultCategory()), messageID, message) + #endif // hifi_LogHandler_h diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 1a0a19bf44..b1ceab4149 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -46,7 +46,7 @@ void printOctalCode(const unsigned char* octalCode) { } char sectionValue(const unsigned char* startByte, char startIndexInByte) { - char rightShift = 8 - startIndexInByte - 3; + int8_t rightShift = 8 - startIndexInByte - 3; if (rightShift < 0) { return ((startByte[0] << -rightShift) & 7) + (startByte[1] >> (8 + rightShift)); @@ -73,7 +73,7 @@ int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsi return sectionValue(descendantOctalCode + 1 + (branchStartBit / 8), branchStartBit % 8); } -unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber) { +unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber) { // find the length (in number of three bit code sequences) // in the parent @@ -111,7 +111,7 @@ unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNu // calculate the amount of left shift required // this will be -1 or -2 if there's wrap - char leftShift = 8 - (startBit % 8) - 3; + int8_t leftShift = 8 - (startBit % 8) - 3; if (leftShift < 0) { // we have a wrap-around to accomodate @@ -248,22 +248,6 @@ void setOctalCodeSectionValue(unsigned char* octalCode, int section, char sectio } } -unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels) { - int codeLength = numberOfThreeBitSectionsInCode(originalOctalCode); - unsigned char* newCode = NULL; - if (codeLength > chopLevels) { - int newLength = codeLength - chopLevels; - newCode = new unsigned char[newLength+1]; - *newCode = newLength; // set the length byte - - for (int section = chopLevels; section < codeLength; section++) { - char sectionValue = getOctalCodeSectionValue(originalOctalCode, section); - setOctalCodeSectionValue(newCode, section - chopLevels, sectionValue); - } - } - return newCode; -} - bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) { if (!possibleAncestor || !possibleDescendent) { return false; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index a0d86f32d2..63cbc58cfa 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -30,7 +30,7 @@ using OctalCodePtrList = std::vector; void printOctalCode(const unsigned char* octalCode); size_t bytesRequiredForCodeLength(unsigned char threeBitCodes); int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsigned char* descendantOctalCode); -unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber); +unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber); const int OVERFLOWED_OCTCODE_BUFFER = -1; const int UNKNOWN_OCTCODE_LENGTH = -2; @@ -40,8 +40,6 @@ const int UNKNOWN_OCTCODE_LENGTH = -2; /// \param int maxBytes number of bytes that octalCode is expected to be, -1 if unknown int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH); -unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels); - const int CHECK_NODE_ONLY = -1; bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild = CHECK_NODE_ONLY); diff --git a/libraries/shared/src/Profile.cpp b/libraries/shared/src/Profile.cpp index 97def2015a..f3cbbf9262 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -27,6 +27,7 @@ Q_LOGGING_CATEGORY(trace_simulation_animation, "trace.simulation.animation") Q_LOGGING_CATEGORY(trace_simulation_animation_detail, "trace.simulation.animation.detail") Q_LOGGING_CATEGORY(trace_simulation_physics, "trace.simulation.physics") Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.detail") +Q_LOGGING_CATEGORY(trace_startup, "trace.startup") Q_LOGGING_CATEGORY(trace_workload, "trace.workload") #if defined(NSIGHT_FOUND) diff --git a/libraries/shared/src/Profile.h b/libraries/shared/src/Profile.h index f2a747afa3..e78ce210c9 100644 --- a/libraries/shared/src/Profile.h +++ b/libraries/shared/src/Profile.h @@ -32,6 +32,7 @@ Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation_detail) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics_detail) +Q_DECLARE_LOGGING_CATEGORY(trace_startup) Q_DECLARE_LOGGING_CATEGORY(trace_workload) class Duration { diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index 08df55dddd..022dd99200 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -47,7 +47,7 @@ protected: bool _doAbortTask{ false }; }; -// JobContext class is the base calss for the context object which is passed through all the Job::run calls thoughout the graph of jobs +// JobContext class is the base class for the context object which is passed through all the Job::run calls thoughout the graph of jobs // It is used to communicate to the job::run its context and various state information the job relies on. // It specifically provide access to: // - The taskFlow object allowing for messaging control flow commands from within a Job::run @@ -73,19 +73,21 @@ class JobConcept { public: using Config = JobConfig; - JobConcept(QConfigPointer config) : _config(config) {} + JobConcept(const std::string& name, QConfigPointer config) : _config(config), _name(name) {} virtual ~JobConcept() = default; + + const std::string& getName() const { return _name; } virtual const Varying getInput() const { return Varying(); } virtual const Varying getOutput() const { return Varying(); } virtual QConfigPointer& getConfiguration() { return _config; } virtual void applyConfiguration() = 0; - void setCPURunTime(double mstime) { std::static_pointer_cast(_config)->setCPURunTime(mstime); } QConfigPointer _config; protected: + const std::string _name; }; @@ -122,7 +124,7 @@ public: class Concept : public JobConcept { public: - Concept(QConfigPointer config) : JobConcept(config) {} + Concept(const std::string& name, QConfigPointer config) : JobConcept(name, config) {} virtual ~Concept() = default; virtual void run(const ContextPointer& jobContext) = 0; @@ -143,8 +145,8 @@ public: const Varying getOutput() const override { return _output; } template - Model(const Varying& input, QConfigPointer config, A&&... args) : - Concept(config), + Model(const std::string& name, const Varying& input, QConfigPointer config, A&&... args) : + Concept(name, config), _data(Data(std::forward(args)...)), _input(input), _output(Output()) { @@ -152,12 +154,14 @@ public: } template - static std::shared_ptr create(const Varying& input, A&&... args) { - return std::make_shared(input, std::make_shared(), std::forward(args)...); + static std::shared_ptr create(const std::string& name, const Varying& input, A&&... args) { + return std::make_shared(name, input, std::make_shared(), std::forward(args)...); } void applyConfiguration() override { + Duration profileRange(trace_render(), ("configure::" + JobConcept::getName()).c_str()); + jobConfigure(_data, *std::static_pointer_cast(Concept::_config)); } @@ -173,8 +177,9 @@ public: template using ModelO = Model; template using ModelIO = Model; - Job(std::string name, ConceptPointer concept) : _concept(concept), _name(name) {} + Job(ConceptPointer concept) : _concept(concept) {} + const std::string& getName() const { return _concept->getName(); } const Varying getInput() const { return _concept->getInput(); } const Varying getOutput() const { return _concept->getOutput(); } QConfigPointer& getConfiguration() const { return _concept->getConfiguration(); } @@ -193,9 +198,9 @@ public: } virtual void run(const ContextPointer& jobContext) { - PerformanceTimer perfTimer(_name.c_str()); + PerformanceTimer perfTimer(getName().c_str()); // NOTE: rather than use the PROFILE_RANGE macro, we create a Duration manually - Duration profileRange(jobContext->profileCategory, _name.c_str()); + Duration profileRange(jobContext->profileCategory, ("run::" + getName()).c_str()); auto start = usecTimestampNow(); _concept->run(jobContext); @@ -203,11 +208,8 @@ public: _concept->setCPURunTime((double)(usecTimestampNow() - start) / 1000.0); } - const std::string& getName() const { return _name; } - protected: ConceptPointer _concept; - std::string _name = ""; }; @@ -230,7 +232,7 @@ public: using ConceptPointer = typename JobType::ConceptPointer; using Jobs = std::vector; - Task(std::string name, ConceptPointer concept) : JobType(name, concept) {} + Task(ConceptPointer concept) : JobType(concept) {} class TaskConcept : public Concept { public: @@ -259,11 +261,11 @@ public: return jobIt; } - TaskConcept(const Varying& input, QConfigPointer config) : Concept(config), _input(input) {} + TaskConcept(const std::string& name, const Varying& input, QConfigPointer config) : Concept(name, config), _input(input) {} // Create a new job in the container's queue; returns the job's output template const Varying addJob(std::string name, const Varying& input, NA&&... args) { - _jobs.emplace_back(name, (NT::JobModel::create(input, std::forward(args)...))); + _jobs.emplace_back((NT::JobModel::create(name, input, std::forward(args)...))); // Conect the child config to this task's config std::static_pointer_cast(Concept::getConfiguration())->connectChildConfig(_jobs.back().getConfiguration(), name); @@ -284,16 +286,18 @@ public: Data _data; - TaskModel(const Varying& input, QConfigPointer config) : - TaskConcept(input, config), + TaskModel(const std::string& name, const Varying& input, QConfigPointer config) : + TaskConcept(name, input, config), _data(Data()) {} template - static std::shared_ptr create(const Varying& input, A&&... args) { - auto model = std::make_shared(input, std::make_shared()); - - model->_data.build(*(model), model->_input, model->_output, std::forward(args)...); + static std::shared_ptr create(const std::string& name, const Varying& input, A&&... args) { + auto model = std::make_shared(name, input, std::make_shared()); + { + Duration profileRange(trace_render(), ("build::" + model->getName()).c_str()); + model->_data.build(*(model), model->_input, model->_output, std::forward(args)...); + } // Recreate the Config to use the templated type model->createConfiguration(); model->applyConfiguration(); @@ -302,9 +306,9 @@ public: } template - static std::shared_ptr create(A&&... args) { + static std::shared_ptr create(const std::string& name, A&&... args) { const auto input = Varying(Input()); - return create(input, std::forward(args)...); + return create(name, input, std::forward(args)...); } void createConfiguration() { @@ -326,6 +330,7 @@ public: } void applyConfiguration() override { + Duration profileRange(trace_render(), ("configure::" + JobConcept::getName()).c_str()); jobConfigure(_data, *std::static_pointer_cast(Concept::_config)); for (auto& job : TaskConcept::_jobs) { job.applyConfiguration(); diff --git a/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp index 6d54351743..58dc971cb9 100644 --- a/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp +++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp @@ -34,48 +34,140 @@ PluginContainer::~PluginContainer() { INSTANCE = nullptr; }; +struct MenuCache { + QSet menus; + struct Item { + QString path; + std::function onClicked; + bool checkable; + bool checked; + QString groupName; + }; + QHash items; + std::map _exclusiveGroups; + + void addMenu(ui::Menu* menu, const QString& menuName) { + if (!menu) { + menus.insert(menuName); + return; + } + + flushCache(menu); + menu->addMenu(menuName); + } + + void removeMenu(ui::Menu* menu, const QString& menuName) { + if (!menu) { + menus.remove(menuName); + return; + } + flushCache(menu); + menu->removeMenu(menuName); + } + + void addMenuItem(ui::Menu* menu, const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { + if (!menu) { + items[name] = Item{ path, onClicked, checkable, checked, groupName }; + return; + } + flushCache(menu); + MenuWrapper* parentItem = menu->getMenu(path); + QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); + if (!groupName.isEmpty()) { + QActionGroup* group{ nullptr }; + if (!_exclusiveGroups.count(groupName)) { + group = _exclusiveGroups[groupName] = new QActionGroup(menu); + group->setExclusive(true); + } else { + group = _exclusiveGroups[groupName]; + } + group->addAction(action); + } + QObject::connect(action, &QAction::triggered, [=] { + onClicked(action->isChecked()); + }); + action->setCheckable(checkable); + action->setChecked(checked); + } + void removeMenuItem(ui::Menu* menu, const QString& menuName, const QString& menuItemName) { + if (!menu) { + items.remove(menuItemName); + return; + } + flushCache(menu); + menu->removeMenuItem(menuName, menuItemName); + } + + bool isOptionChecked(ui::Menu* menu, const QString& name) { + if (!menu) { + return items.contains(name) && items[name].checked; + } + flushCache(menu); + return menu->isOptionChecked(name); + } + + void setIsOptionChecked(ui::Menu* menu, const QString& name, bool checked) { + if (!menu) { + if (items.contains(name)) { + items[name].checked = checked; + } + return; + } + flushCache(menu); + + } + + void flushCache(ui::Menu* menu) { + if (!menu) { + return; + } + static bool flushed = false; + if (flushed) { + return; + } + flushed = true; + for (const auto& menuName : menus) { + addMenu(menu, menuName); + } + menus.clear(); + + for (const auto& menuItemName : items.keys()) { + const auto menuItem = items[menuItemName]; + addMenuItem(menu, menuItem.path, menuItemName, menuItem.onClicked, menuItem.checkable, menuItem.checked, menuItem.groupName); + } + items.clear(); + } +}; + + +static MenuCache& getMenuCache() { + static MenuCache cache; + return cache; +} void PluginContainer::addMenu(const QString& menuName) { - getPrimaryMenu()->addMenu(menuName); + getMenuCache().addMenu(getPrimaryMenu(), menuName); } void PluginContainer::removeMenu(const QString& menuName) { - getPrimaryMenu()->removeMenu(menuName); + getMenuCache().removeMenu(getPrimaryMenu(), menuName); } -QAction* PluginContainer::addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { - auto menu = getPrimaryMenu(); - MenuWrapper* parentItem = menu->getMenu(path); - QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name); - if (!groupName.isEmpty()) { - QActionGroup* group { nullptr }; - if (!_exclusiveGroups.count(groupName)) { - group = _exclusiveGroups[groupName] = new QActionGroup(menu); - group->setExclusive(true); - } else { - group = _exclusiveGroups[groupName]; - } - group->addAction(action); - } - QObject::connect(action, &QAction::triggered, [=] { - onClicked(action->isChecked()); - }); - action->setCheckable(checkable); - action->setChecked(checked); +void PluginContainer::addMenuItem(PluginType type, const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { + getMenuCache().addMenuItem(getPrimaryMenu(), path, name, onClicked, checkable, checked, groupName); if (type == PluginType::DISPLAY_PLUGIN) { _currentDisplayPluginActions.push_back({ path, name }); } else { _currentInputPluginActions.push_back({ path, name }); } - return action; } void PluginContainer::removeMenuItem(const QString& menuName, const QString& menuItem) { - getPrimaryMenu()->removeMenuItem(menuName, menuItem); + getMenuCache().removeMenuItem(getPrimaryMenu(), menuName, menuItem); } bool PluginContainer::isOptionChecked(const QString& name) { - return getPrimaryMenu()->isOptionChecked(name); + return getMenuCache().isOptionChecked(getPrimaryMenu(), name); } void PluginContainer::setIsOptionChecked(const QString& path, bool checked) { @@ -161,3 +253,7 @@ void PluginContainer::setBoolSetting(const QString& settingName, bool value) { Setting::Handle settingValue(settingName, value); return settingValue.set(value); } + +void PluginContainer::flushMenuUpdates() { + getMenuCache().flushCache(getPrimaryMenu()); +} diff --git a/libraries/ui-plugins/src/ui-plugins/PluginContainer.h b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h index 167af100b3..da9ea46cf4 100644 --- a/libraries/ui-plugins/src/ui-plugins/PluginContainer.h +++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.h @@ -46,7 +46,7 @@ public: void addMenu(const QString& menuName); void removeMenu(const QString& menuName); - QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = ""); + void addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = ""); void removeMenuItem(const QString& menuName, const QString& menuItem); bool isOptionChecked(const QString& name); void setIsOptionChecked(const QString& path, bool checked); @@ -77,9 +77,9 @@ public: } protected: + void flushMenuUpdates(); QVector> _currentDisplayPluginActions; QVector> _currentInputPluginActions; - std::map _exclusiveGroups; QRect _savedGeometry { 10, 120, 800, 600 }; }; diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index 091fd850af..f9fc71e417 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -79,12 +79,12 @@ void MainWindow::closeEvent(QCloseEvent* event) { } void MainWindow::moveEvent(QMoveEvent* event) { - emit windowGeometryChanged(QRect(event->pos(), size())); + emit windowGeometryChanged(QRect(QPoint(geometry().x(), geometry().y()), size())); // Geometry excluding the window frame. QMainWindow::moveEvent(event); } void MainWindow::resizeEvent(QResizeEvent* event) { - emit windowGeometryChanged(QRect(QPoint(x(), y()), event->size())); + emit windowGeometryChanged(QRect(QPoint(geometry().x(), geometry().y()), size())); // Geometry excluding the window frame. QMainWindow::resizeEvent(event); } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 7a938f39c8..a1d09139e3 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -154,6 +154,13 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); if (!item) { diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index cb8ee29068..26f9f58dd5 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -60,6 +60,7 @@ public: void createDesktop(const QUrl& url); void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void hide(const QString& name); + void hideDesktopWindows(); bool isVisible(const QString& name); void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); bool shouldSwallowShortcut(QEvent* event); diff --git a/libraries/ui/src/VirtualPadManager.cpp b/libraries/ui/src/VirtualPadManager.cpp index cb3ef20e67..c786110bdf 100644 --- a/libraries/ui/src/VirtualPadManager.cpp +++ b/libraries/ui/src/VirtualPadManager.cpp @@ -35,9 +35,13 @@ namespace VirtualPad { } const float Manager::DPI = 534.0f; - const float Manager::PIXEL_SIZE = 512.0f; - const float Manager::STICK_RADIUS = 105.0f; - const float Manager::BASE_MARGIN = 59.0f; + const float Manager::BASE_DIAMETER_PIXELS = 512.0f; + const float Manager::BASE_MARGIN_PIXELS = 59.0f; + const float Manager::STICK_RADIUS_PIXELS = 105.0f; + const float Manager::JUMP_BTN_TRIMMED_RADIUS_PIXELS = 67.0f; + const float Manager::JUMP_BTN_FULL_PIXELS = 134.0f; + const float Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS = 67.0f; + const float Manager::JUMP_BTN_LEFT_MARGIN_PIXELS = 547.0f; Manager::Manager() { @@ -72,6 +76,14 @@ namespace VirtualPad { _extraBottomMargin = margin; } + glm::vec2 Manager::getJumpButtonPosition() { + return _jumpButtonPosition; + } + + void Manager::setJumpButtonPosition(glm::vec2 point) { + _jumpButtonPosition = point; + } + Instance* Manager::getLeftVirtualPad() { return &_leftVPadInstance; } diff --git a/libraries/ui/src/VirtualPadManager.h b/libraries/ui/src/VirtualPadManager.h index 6b68af3acd..68b3d4f10f 100644 --- a/libraries/ui/src/VirtualPadManager.h +++ b/libraries/ui/src/VirtualPadManager.h @@ -44,16 +44,23 @@ namespace VirtualPad { void hide(bool hide); int extraBottomMargin(); void setExtraBottomMargin(int margin); + glm::vec2 getJumpButtonPosition(); + void setJumpButtonPosition(glm::vec2 point); static const float DPI; - static const float PIXEL_SIZE; - static const float STICK_RADIUS; - static const float BASE_MARGIN; + static const float BASE_DIAMETER_PIXELS; + static const float BASE_MARGIN_PIXELS; + static const float STICK_RADIUS_PIXELS; + static const float JUMP_BTN_TRIMMED_RADIUS_PIXELS; + static const float JUMP_BTN_FULL_PIXELS; + static const float JUMP_BTN_BOTTOM_MARGIN_PIXELS; + static const float JUMP_BTN_LEFT_MARGIN_PIXELS; private: Instance _leftVPadInstance; bool _enabled; bool _hidden; + glm::vec2 _jumpButtonPosition; int _extraBottomMargin {0}; }; } diff --git a/libraries/ui/src/ui/ImageProvider.cpp b/libraries/ui/src/ui/ImageProvider.cpp deleted file mode 100644 index 7bbad43f2e..0000000000 --- a/libraries/ui/src/ui/ImageProvider.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// ImageProvider.cpp -// interface/src/ui -// -// Created by David Kelly on 8/23/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "ImageProvider.h" - -#include -#include - -const QString ImageProvider::PROVIDER_NAME = "security"; -QReadWriteLock ImageProvider::_rwLock; -QPixmap* ImageProvider::_securityImage = nullptr; - -ImageProvider::~ImageProvider() { - QWriteLocker lock(&_rwLock); - if (_securityImage) { - delete _securityImage; - _securityImage = nullptr; - } -} - -void ImageProvider::setSecurityImage(const QPixmap* pixmap) { - // no need to delete old one, that is managed by the wallet - QWriteLocker lock(&_rwLock); - if (_securityImage) { - delete _securityImage; - } - if (pixmap) { - _securityImage = new QPixmap(*pixmap); - } else { - _securityImage = nullptr; - } -} - -QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) { - - // adjust the internal pixmap to have the requested size - QReadLocker lock(&_rwLock); - if (id == "securityImage" && _securityImage) { - *size = _securityImage->size(); - if (requestedSize.width() > 0 && requestedSize.height() > 0) { - return _securityImage->scaled(requestedSize.width(), requestedSize.height(), Qt::KeepAspectRatio); - } else { - return _securityImage->copy(); - } - } - // otherwise just return a grey pixmap. This avoids annoying error messages in qml we would get - // when sending a 'null' pixmap (QPixmap()) - QPixmap greyPixmap(200, 200); - greyPixmap.fill(QColor("darkGrey")); - return greyPixmap; -} diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 12e9b8b87c..43b573a169 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -48,7 +48,7 @@ #include #include -#include "ImageProvider.h" +#include "SecurityImageProvider.h" #include "types/FileTypeProfile.h" #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" @@ -233,8 +233,8 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { qmlRegisterType("Hifi", 1, 0, "SoundEffect"); }); - // register the pixmap image provider (used only for security image, for now) - engine->addImageProvider(ImageProvider::PROVIDER_NAME, new ImageProvider()); + // Register the pixmap Security Image Provider + engine->addImageProvider(SecurityImageProvider::PROVIDER_NAME, new SecurityImageProvider()); engine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); auto importList = engine->importPathList(); @@ -256,6 +256,15 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { #if !defined(Q_OS_ANDROID) rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); + { + PROFILE_RANGE(startup, "FileTypeProfile"); + rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); + } + { + PROFILE_RANGE(startup, "HFWebEngineProfile"); + rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); + + } #endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); rootContext->setContextProperty("Tablet", DependencyManager::get().data()); @@ -335,6 +344,7 @@ void OffscreenQmlSurface::onRootCreated() { tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this); QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); getSurfaceContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); + getSurfaceContext()->engine()->addImageProvider(SecurityImageProvider::PROVIDER_NAME, new SecurityImageProvider()); } QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); } diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 7efa36624b..51fe11fdc7 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -12,6 +12,7 @@ #include #include "OffscreenQmlSurface.h" +#include "Profile.h" OffscreenQmlSurfaceCache::OffscreenQmlSurfaceCache() { } @@ -38,6 +39,7 @@ void OffscreenQmlSurfaceCache::reserve(const QString& rootSource, int count) { } void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedPointer& surface) { + PROFILE_RANGE(app, "buildSurface"); surface->pause(); _cache[rootSource].push_back(surface); } diff --git a/libraries/ui/src/ui/SecurityImageProvider.cpp b/libraries/ui/src/ui/SecurityImageProvider.cpp new file mode 100644 index 0000000000..190e58d6e8 --- /dev/null +++ b/libraries/ui/src/ui/SecurityImageProvider.cpp @@ -0,0 +1,52 @@ +// +// SecurityImageProvider.cpp +// interface/src/ui +// +// Created by David Kelly on 8/23/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SecurityImageProvider.h" + +#include +#include + +const QString SecurityImageProvider::PROVIDER_NAME = "security"; + +SecurityImageProvider::~SecurityImageProvider() { +} + +void SecurityImageProvider::setSecurityImage(const QPixmap* pixmap) { + // no need to delete old one, that is managed by the wallet + QWriteLocker lock(&_rwLock); + + if (pixmap) { + _securityImage = pixmap->copy(); + } else { + QPixmap greyPixmap(200, 200); + greyPixmap.fill(QColor("darkGrey")); + _securityImage = greyPixmap.copy(); + } +} + +QPixmap SecurityImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) { + + // adjust the internal pixmap to have the requested size + QReadLocker lock(&_rwLock); + if (id == "securityImage") { + *size = _securityImage.size(); + if (requestedSize.width() > 0 && requestedSize.height() > 0) { + return _securityImage.scaled(requestedSize.width(), requestedSize.height(), Qt::KeepAspectRatio); + } else { + return _securityImage.copy(); + } + } + // otherwise just return a grey pixmap. This avoids annoying error messages in qml we would get + // when sending a 'null' pixmap (QPixmap()) + QPixmap greyPixmap(200, 200); + greyPixmap.fill(QColor("darkGrey")); + return greyPixmap; +} diff --git a/libraries/ui/src/ui/ImageProvider.h b/libraries/ui/src/ui/SecurityImageProvider.h similarity index 57% rename from libraries/ui/src/ui/ImageProvider.h rename to libraries/ui/src/ui/SecurityImageProvider.h index 0093b60655..b12932e225 100644 --- a/libraries/ui/src/ui/ImageProvider.h +++ b/libraries/ui/src/ui/SecurityImageProvider.h @@ -1,5 +1,5 @@ // -// ImageProvider.h +// SecurityImageProvider.h // // Created by David Kelly on 2017/08/23 // Copyright 2017 High Fidelity, Inc. @@ -9,26 +9,25 @@ // #pragma once -#ifndef hifi_ImageProvider_h -#define hifi_ImageProvider_h +#ifndef hifi_SecurityImageProvider_h +#define hifi_SecurityImageProvider_h #include #include -class ImageProvider: public QQuickImageProvider { +class SecurityImageProvider: public QQuickImageProvider { public: static const QString PROVIDER_NAME; - ImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {} - virtual ~ImageProvider(); + SecurityImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {} + virtual ~SecurityImageProvider(); QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; void setSecurityImage(const QPixmap* pixmap); protected: - static QReadWriteLock _rwLock; - static QPixmap* _securityImage; - + QReadWriteLock _rwLock; + QPixmap _securityImage; }; -#endif //hifi_ImageProvider_h +#endif //hifi_SecurityImageProvider_h diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 0191baca5e..a079609bb7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -387,6 +387,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { offscreenUi->hide("RunningScripts"); _showRunningScripts = true; } + + offscreenUi->hideDesktopWindows(); // destroy desktop window if (_desktopWindow) { _desktopWindow->deleteLater(); diff --git a/script-archive/example/audio/audioReverbOn.js b/script-archive/example/audio/audioReverbOn.js deleted file mode 100644 index 72f2fa97bc..0000000000 --- a/script-archive/example/audio/audioReverbOn.js +++ /dev/null @@ -1,43 +0,0 @@ -// -// audioReverbOn.js -// examples -// -// Copyright 2014 High Fidelity, Inc. -// -// -// Gives the ability to be set various reverb settings. -// -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -// http://wiki.audacityteam.org/wiki/GVerb#Instant_reverb_settings -var audioOptions = new AudioEffectOptions({ - // Square Meters - maxRoomSize: 50, - roomSize: 50, - - // Seconds - reverbTime: 10, - - // Between 0 - 1 - damping: 0.50, - inputBandwidth: 0.75, - - // dB - earlyLevel: -22, - tailLevel: -28, - dryLevel: 0, - wetLevel: 6 -}); - -AudioDevice.setReverbOptions(audioOptions); -AudioDevice.setReverb(true); -print("Reverb is now on with the updated options."); - -function scriptEnding() { - AudioDevice.setReverb(false); - print("Reberb is now off."); -} - -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/script-archive/inspect.js b/script-archive/inspect.js index 555b4105b7..18b26ab709 100644 --- a/script-archive/inspect.js +++ b/script-archive/inspect.js @@ -26,14 +26,14 @@ var RADIUS_RATE = 1.0 / 100.0; var PAN_RATE = 250.0; var Y_AXIS = { - x: 0, - y: 1, - z: 0 + x: 0, + y: 1, + z: 0 }; var X_AXIS = { - x: 1, - y: 0, - z: 0 + x: 1, + y: 0, + z: 0 }; var LOOK_AT_TIME = 500; @@ -56,21 +56,20 @@ var mode = noMode; var mouseLastX = 0; var mouseLastY = 0; - var center = { - x: 0, - y: 0, - z: 0 + x: 0, + y: 0, + z: 0 }; var position = { - x: 0, - y: 0, - z: 0 + x: 0, + y: 0, + z: 0 }; var vector = { - x: 0, - y: 0, - z: 0 + x: 0, + y: 0, + z: 0 }; var radius = 0.0; var azimuth = 0.0; @@ -83,258 +82,248 @@ var rotatingTowardsTarget = false; var targetCamOrientation; var oldPosition, oldOrientation; - function orientationOf(vector) { - var direction, - yaw, - pitch; + var direction, + yaw, + pitch; - direction = Vec3.normalize(vector); - yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); - pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); - return Quat.multiply(yaw, pitch); + direction = Vec3.normalize(vector); + yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); + pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); + return Quat.multiply(yaw, pitch); } - function handleRadialMode(dx, dy) { - azimuth += dx / AZIMUTH_RATE; - radius += radius * dy * RADIUS_RATE; - if (radius < 1) { - radius = 1; - } + azimuth += dx / AZIMUTH_RATE; + radius += radius * dy * RADIUS_RATE; + if (radius < 1) { + radius = 1; + } - vector = { - x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, - y: Math.sin(altitude) * radius, - z: (Math.cos(altitude) * Math.sin(azimuth)) * radius - }; - position = Vec3.sum(center, vector); - Camera.setPosition(position); - Camera.setOrientation(orientationOf(vector)); + vector = { + x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, + y: Math.sin(altitude) * radius, + z: (Math.cos(altitude) * Math.sin(azimuth)) * radius + }; + position = Vec3.sum(center, vector); + Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } function handleOrbitMode(dx, dy) { - azimuth += dx / AZIMUTH_RATE; - altitude += dy / ALTITUDE_RATE; - if (altitude > PI / 2.0) { - altitude = PI / 2.0; - } - if (altitude < -PI / 2.0) { - altitude = -PI / 2.0; - } + azimuth += dx / AZIMUTH_RATE; + altitude += dy / ALTITUDE_RATE; + if (altitude > PI / 2.0) { + altitude = PI / 2.0; + } + if (altitude < -PI / 2.0) { + altitude = -PI / 2.0; + } - vector = { - x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, - y: Math.sin(altitude) * radius, - z: (Math.cos(altitude) * Math.sin(azimuth)) * radius - }; - position = Vec3.sum(center, vector); - Camera.setPosition(position); - Camera.setOrientation(orientationOf(vector)); + vector = { + x: (Math.cos(altitude) * Math.cos(azimuth)) * radius, + y: Math.sin(altitude) * radius, + z: (Math.cos(altitude) * Math.sin(azimuth)) * radius + }; + position = Vec3.sum(center, vector); + Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } - function handlePanMode(dx, dy) { - var up = Quat.getUp(Camera.getOrientation()); - var right = Quat.getRight(Camera.getOrientation()); - var distance = Vec3.length(vector); + var up = Quat.getUp(Camera.getOrientation()); + var right = Quat.getRight(Camera.getOrientation()); + var distance = Vec3.length(vector); - var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE)); + var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE)); - center = Vec3.sum(center, dv); - position = Vec3.sum(position, dv); + center = Vec3.sum(center, dv); + position = Vec3.sum(position, dv); - Camera.setPosition(position); - Camera.setOrientation(orientationOf(vector)); + Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } function saveCameraState() { - oldMode = Camera.mode; - oldPosition = Camera.getPosition(); - oldOrientation = Camera.getOrientation(); + oldMode = Camera.mode; + oldPosition = Camera.getPosition(); + oldOrientation = Camera.getOrientation(); - Camera.mode = "independent"; - Camera.setPosition(oldPosition); + Camera.mode = "independent"; + Camera.setPosition(oldPosition); } function restoreCameraState() { - Camera.mode = oldMode; - Camera.setPosition(oldPosition); - Camera.setOrientation(oldOrientation); + Camera.mode = oldMode; + Camera.setPosition(oldPosition); + Camera.setOrientation(oldOrientation); } function handleModes() { - var newMode = (mode == noMode) ? noMode : detachedMode; - if (alt) { - if (control) { - if (shift) { - newMode = panningMode; - } else { - newMode = orbitMode; - } - } else { - newMode = radialMode; + var newMode = (mode == noMode) ? noMode : detachedMode; + if (alt) { + if (control) { + if (shift) { + newMode = panningMode; + } else { + newMode = orbitMode; + } + } else { + newMode = radialMode; + } } - } - // if entering detachMode - if (newMode == detachedMode && mode != detachedMode) { - avatarPosition = MyAvatar.position; - avatarOrientation = MyAvatar.orientation; - } - // if leaving detachMode - if (mode == detachedMode && newMode == detachedMode && - (avatarPosition.x != MyAvatar.position.x || - avatarPosition.y != MyAvatar.position.y || - avatarPosition.z != MyAvatar.position.z || - avatarOrientation.x != MyAvatar.orientation.x || - avatarOrientation.y != MyAvatar.orientation.y || - avatarOrientation.z != MyAvatar.orientation.z || - avatarOrientation.w != MyAvatar.orientation.w)) { - newMode = noMode; - } + // if entering detachMode + if (newMode == detachedMode && mode != detachedMode) { + avatarPosition = MyAvatar.position; + avatarOrientation = MyAvatar.orientation; + } + // if leaving detachMode + if (mode == detachedMode && newMode == detachedMode && + (avatarPosition.x != MyAvatar.position.x || + avatarPosition.y != MyAvatar.position.y || + avatarPosition.z != MyAvatar.position.z || + avatarOrientation.x != MyAvatar.orientation.x || + avatarOrientation.y != MyAvatar.orientation.y || + avatarOrientation.z != MyAvatar.orientation.z || + avatarOrientation.w != MyAvatar.orientation.w)) { + newMode = noMode; + } - if (mode == noMode && newMode != noMode && Camera.mode == "independent") { - newMode = noMode; - } + if (mode == noMode && newMode != noMode && Camera.mode == "independent") { + newMode = noMode; + } - // if leaving noMode - if (mode == noMode && newMode != noMode) { - saveCameraState(); - } - // if entering noMode - if (newMode == noMode && mode != noMode) { - restoreCameraState(); - } + // if leaving noMode + if (mode == noMode && newMode != noMode) { + saveCameraState(); + } + // if entering noMode + if (newMode == noMode && mode != noMode) { + restoreCameraState(); + } - mode = newMode; + mode = newMode; } function keyPressEvent(event) { - var changed = false; + var changed = false; - if (event.text == "ALT") { - alt = true; - changed = true; - } - if (event.text == "CONTROL") { - control = true; - changed = true; - } - if (event.text == "SHIFT") { - shift = true; - changed = true; - } + if (event.text == "ALT") { + alt = true; + changed = true; + } + if (event.text == "CONTROL") { + control = true; + changed = true; + } + if (event.text == "SHIFT") { + shift = true; + changed = true; + } - if (changed) { - handleModes(); - } + if (changed) { + handleModes(); + } } function keyReleaseEvent(event) { - var changed = false; + var changed = false; - if (event.text == "ALT") { - alt = false; - changed = true; - mode = noMode; - restoreCameraState(); - } - if (event.text == "CONTROL") { - control = false; - changed = true; - } - if (event.text == "SHIFT") { - shift = false; - changed = true; - } - - if (changed) { - handleModes(); - } -} - - - -function mousePressEvent(event) { - if (alt && !isActive) { - mouseLastX = event.x; - mouseLastY = event.y; - - // Compute trajectories related values - var pickRay = Camera.computePickRay(mouseLastX, mouseLastY); - var modelIntersection = Entities.findRayIntersection(pickRay, true); - - position = Camera.getPosition(); - - var avatarTarget = MyAvatar.getTargetAvatarPosition(); - - - var distance = -1; - var string; - - if (modelIntersection.intersects && modelIntersection.accurate) { - distance = modelIntersection.distance; - center = modelIntersection.intersection; - string = "Inspecting model"; - //We've selected our target, now orbit towards it automatically - rotatingTowardsTarget = true; - //calculate our target cam rotation - Script.setTimeout(function() { - rotatingTowardsTarget = false; - }, LOOK_AT_TIME); - - vector = Vec3.subtract(position, center); - targetCamOrientation = orientationOf(vector); - radius = Vec3.length(vector); - azimuth = Math.atan2(vector.z, vector.x); - altitude = Math.asin(vector.y / Vec3.length(vector)); - - isActive = true; + if (event.text == "ALT") { + alt = false; + changed = true; + mode = noMode; + restoreCameraState(); + } + if (event.text == "CONTROL") { + control = false; + changed = true; + } + if (event.text == "SHIFT") { + shift = false; + changed = true; } - } + if (changed) { + handleModes(); + } +} + +function mousePressEvent(event) { + if (alt && !isActive) { + mouseLastX = event.x; + mouseLastY = event.y; + + // Compute trajectories related values + var pickRay = Camera.computePickRay(mouseLastX, mouseLastY); + var modelIntersection = Entities.findRayIntersection(pickRay, true); + var avatarIntersection = AvatarList.findRayIntersection(pickRay); + + position = Camera.getPosition(); + + if (avatarIntersection.intersects || (modelIntersection.intersects && modelIntersection.accurate)) { + if (avatarIntersection.intersects) { + center = avatarIntersection.intersection; + } else { + center = modelIntersection.intersection; + } + // We've selected our target, now orbit towards it automatically + rotatingTowardsTarget = true; + // calculate our target cam rotation + Script.setTimeout(function () { + rotatingTowardsTarget = false; + }, LOOK_AT_TIME); + + vector = Vec3.subtract(position, center); + targetCamOrientation = orientationOf(vector); + radius = Vec3.length(vector); + azimuth = Math.atan2(vector.z, vector.x); + altitude = Math.asin(vector.y / Vec3.length(vector)); + + isActive = true; + } + } } function mouseReleaseEvent(event) { - if (isActive) { - isActive = false; - } + if (isActive) { + isActive = false; + } } function mouseMoveEvent(event) { - if (isActive && mode != noMode && !rotatingTowardsTarget) { - if (mode == radialMode) { - handleRadialMode(event.x - mouseLastX, event.y - mouseLastY); + if (isActive && mode != noMode && !rotatingTowardsTarget) { + if (mode == radialMode) { + handleRadialMode(event.x - mouseLastX, event.y - mouseLastY); + } + if (mode == orbitMode) { + handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY); + } + if (mode == panningMode) { + handlePanMode(event.x - mouseLastX, event.y - mouseLastY); + } } - if (mode == orbitMode) { - handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY); - } - if (mode == panningMode) { - handlePanMode(event.x - mouseLastX, event.y - mouseLastY); - } - - } - mouseLastX = event.x; - mouseLastY = event.y; + mouseLastX = event.x; + mouseLastY = event.y; } function update() { - handleModes(); - if (rotatingTowardsTarget) { - rotateTowardsTarget(); - } + handleModes(); + if (rotatingTowardsTarget) { + rotateTowardsTarget(); + } } function rotateTowardsTarget() { - var newOrientation = Quat.mix(Camera.getOrientation(), targetCamOrientation, .1); - Camera.setOrientation(newOrientation); + var newOrientation = Quat.mix(Camera.getOrientation(), targetCamOrientation, 0.1); + Camera.setOrientation(newOrientation); } function scriptEnding() { - if (mode != noMode) { - restoreCameraState(); - } + if (mode != noMode) { + restoreCameraState(); + } } Controller.keyPressEvent.connect(keyPressEvent); @@ -345,4 +334,4 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file +Script.scriptEnding.connect(scriptEnding); diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index a8f6bf42a1..11aee6a9d2 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -16,7 +16,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/+android/touchscreenvirtualpad.js", "system/+android/bottombar.js", "system/+android/audio.js" , - "system/+android/modes.js"/*, + "system/+android/modes.js", + "system/+android/stats.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", diff --git a/scripts/developer/utilities/audio/audioScope.js b/scripts/developer/utilities/audio/audioScope.js index 00c9e4b725..63cbf0a6e2 100644 --- a/scripts/developer/utilities/audio/audioScope.js +++ b/scripts/developer/utilities/audio/audioScope.js @@ -1,4 +1,4 @@ -var qml = Script.resourcesPath() + '/qml/AudioScope.qml'; +var qml = Script.resourcesPath() + '/qml/AudioScopeUI.qml'; var window = new OverlayWindow({ title: 'Audio Scope', source: qml, @@ -14,4 +14,4 @@ window.closed.connect(function () { AudioScope.setServerEcho(false); AudioScope.selectAudioScopeFiveFrames(); Script.stop(); -}); \ No newline at end of file +}); diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index c2173f6e2a..664af836a9 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -157,7 +157,7 @@ }) function cleanup() { - Pointers.removePointer(ray); + Pointers.removePointer(laser); Selection.disableListHighlight(HoveringList) Selection.removeListFromMap(HoveringList) diff --git a/scripts/developer/utilities/render/debugTransition.js b/scripts/developer/utilities/render/debugTransition.js index 161ff3f5d4..450b2e3ac9 100644 --- a/scripts/developer/utilities/render/debugTransition.js +++ b/scripts/developer/utilities/render/debugTransition.js @@ -1,5 +1,3 @@ -"use strict"; - // // debugTransition.js // developer/utilities/render @@ -12,12 +10,17 @@ // (function() { + "use strict"; + var TABLET_BUTTON_NAME = "Transition"; var QMLAPP_URL = Script.resolvePath("./transition.qml"); var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg"); - + Script.include([ + Script.resolvePath("../../../system/libraries/stringHelpers.js"), + ]); + var onScreen = false; function onClicked() { @@ -37,6 +40,71 @@ var hasEventBridge = false; + function enableSphereVisualization() { + if (gradientSphere==undefined) { + gradientSphere = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0), + dimensions: { x: 1.0, y: 1.0, z: 1.0 }, + color: { red: 100, green: 150, blue: 255}, + alpha: 0.2, + solid: false + }); + } + if (noiseSphere==undefined) { + noiseSphere = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0), + dimensions: { x: 1.0, y: 1.0, z: 1.0 }, + color: { red: 255, green: 150, blue: 100}, + alpha: 0.2, + solid: false + }); + } + } + + function disableSphereVisualization() { + Overlays.deleteOverlay(noiseSphere); + Overlays.deleteOverlay(gradientSphere); + noiseSphere = undefined + gradientSphere = undefined + } + + // Create a Laser pointer used to pick and add objects to selections + var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 }; + var COLOR1 = {red: 255, green: 0, blue: 0}; + var COLOR2 = {red: 0, green: 255, blue: 0}; + var end1 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR1, + ignoreRayIntersection: true + } + var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignoreRayIntersection: true + } + var laser + + function enablePointer() { + laser = Pointers.createPointer(PickType.Ray, { + joint: "Mouse", + filter: Picks.PICK_ENTITIES, + renderStates: [{name: "one", end: end1}], + defaultRenderStates: [{name: "one", end: end2, distance: 2.0}], + enabled: true + }); + Pointers.setRenderState(laser, "one"); + Pointers.enablePointer(laser) + } + + function disablePointer() { + Pointers.disablePointer(laser) + Pointers.removePointer(laser); + } + function wireEventBridge(on) { if (!tablet) { print("Warning in wireEventBridge(): 'tablet' undefined!"); @@ -46,11 +114,15 @@ if (!hasEventBridge) { tablet.fromQml.connect(fromQml); hasEventBridge = true; + enablePointer(); + Render.getConfig("RenderMainView.FadeEdit")["editFade"] = true } } else { if (hasEventBridge) { tablet.fromQml.disconnect(fromQml); hasEventBridge = false; + disablePointer(); + Render.getConfig("RenderMainView.FadeEdit")["editFade"] = false } } } @@ -66,12 +138,87 @@ wireEventBridge(onScreen); } + var isEditEnabled = false + var noiseSphere + var gradientSphere + var selectedEntity + var editedCategory + + var FADE_MIN_SCALE = 0.001 + var FADE_MAX_SCALE = 10000.0 + + function parameterToValuePow(parameter, minValue, maxOverMinValue) { + return minValue * Math.pow(maxOverMinValue, parameter); + //return parameter + } + + function update(dt) { + var gradientProperties = Entities.getEntityProperties(selectedEntity, ["position", "dimensions"]); + if (gradientProperties!=undefined) { + var pos = gradientProperties.position + if (pos!=undefined) { + var config = Render.getConfig("RenderMainView.Fade") + //print("Center at "+pos.x+" "+pos.y+" "+pos.z) + var dim = {x:config.baseSizeX, y:config.baseSizeY, z:config.baseSizeZ} + dim.x = parameterToValuePow(dim.x, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.y = parameterToValuePow(dim.y, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.z = parameterToValuePow(dim.z, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + if (editedCategory==4 || editedCategory==5) { + dim.y = gradientProperties.dimensions.y + pos.y = pos.y - gradientProperties.dimensions.y/2 + } + Overlays.editOverlay(gradientSphere, { position: pos, dimensions: dim, alpha: config.baseLevel }) + dim.x = parameterToValuePow(config.noiseSizeX, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.y = parameterToValuePow(config.noiseSizeY, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + dim.z = parameterToValuePow(config.noiseSizeZ, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE) + Overlays.editOverlay(noiseSphere, { position: pos, dimensions: dim, alpha: config.noiseLevel }) + } + } + } + + Script.update.connect(update); + + function loadConfiguration(fileUrl) { + var config = Render.getConfig("RenderMainView.Fade") + config.load(fileUrl) + } + + function saveConfiguration(fileUrl) { + var config = Render.getConfig("RenderMainView.Fade") + config.save(fileUrl) + } + function fromQml(message) { + tokens = message.split('*') + //print("Received '"+message+"' from transition.qml") + command = tokens[0].toLowerCase() + if (command=="category") { + editedCategory = parseInt(tokens[1]) + } else if (command=="save") { + var filePath = tokens[1] + print("Raw token = "+filePath) + if (filePath.startsWith("file:///")) { + filePath = filePath.substr(8) + print("Saving configuration to "+filePath) + saveConfiguration(filePath) + } else { + print("Configurations can only be saved to local files") + } + } else if (command=="load") { + var filePath = tokens[1] + if (filePath.startsWith("file:///")) { + filePath = filePath.substr(8) + print("Loading configuration from "+filePath) + loadConfiguration(filePath) + } else { + print("Configurations can only be loaded from local files") + } + } } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); - + Script.scriptEnding.connect(function () { if (onScreen) { tablet.gotoHomeScreen(); @@ -81,4 +228,28 @@ tablet.removeButton(button); }); + + var currentSelectionName = "" + var SelectionList = "TransitionEdit" + + Selection.enableListToScene(SelectionList) + Selection.clearSelectedItemsList(SelectionList) + + Entities.clickDownOnEntity.connect(function (id, event) { + if (selectedEntity) { + Selection.removeFromSelectedItemsList(SelectionList, "entity", selectedEntity) + } + selectedEntity = id + Selection.addToSelectedItemsList(SelectionList, "entity", selectedEntity) + update() + }) + + function cleanup() { + disablePointer(); + Selection.removeListFromMap(SelectionList) + Selection.disableListToScene(SelectionList); + Overlays.deleteOverlay(noiseSphere); + Overlays.deleteOverlay(gradientSphere); + } + Script.scriptEnding.connect(cleanup); }()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index e83a85f8ed..f74468a273 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -11,6 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 import "qrc:///qml/styles-uit" import "qrc:///qml/controls-uit" as HifiControls @@ -22,11 +23,31 @@ Rectangle { id: root anchors.margins: hifi.dimensions.contentMargin.x + signal sendToScript(var message); + color: hifi.colors.baseGray; property var config: Render.getConfig("RenderMainView.Fade"); property var configEdit: Render.getConfig("RenderMainView.FadeEdit"); + FileDialog { + id: fileDialog + title: "Please choose a file" + folder: shortcuts.documents + nameFilters: [ "JSON files (*.json)", "All files (*)" ] + onAccepted: { + root.sendToScript(title.split(" ")[0]+"*"+fileUrl.toString()) + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 500ms later + paramWidgetLoader.sourceComponent = undefined; + postpone.interval = 500 + postpone.start() + } + onRejected: { + } + Component.onCompleted: visible = false + } + ColumnLayout { spacing: 3 anchors.left: parent.left @@ -37,23 +58,14 @@ Rectangle { } RowLayout { - spacing: 20 + spacing: 8 Layout.fillWidth: true id: root_col - HifiControls.CheckBox { - anchors.verticalCenter: parent.verticalCenter - boxSize: 20 - text: "Edit" - checked: root.configEdit["editFade"] - onCheckedChanged: { - root.configEdit["editFade"] = checked; - Render.getConfig("RenderMainView.DrawFadedOpaqueBounds").enabled = checked; - } - } HifiControls.ComboBox { anchors.verticalCenter: parent.verticalCenter - Layout.fillWidth: true + anchors.left : parent.left + width: 300 id: categoryBox model: ["Elements enter/leave domain", "Bubble isect. - Owner POV", "Bubble isect. - Trespasser POV", "Another user leaves/arrives", "Changing an avatar"] Timer { @@ -61,17 +73,48 @@ Rectangle { interval: 100; running: false; repeat: false onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets + var isTimeBased = categoryBox.currentIndex==0 || categoryBox.currentIndex==3 + paramWidgetLoader.item.isTimeBased = isTimeBased } } onCurrentIndexChanged: { + var descriptions = [ + "Time based threshold, gradients centered on object", + "Fixed threshold, gradients centered on owner avatar", + "Position based threshold (increases when trespasser moves closer to avatar), gradients centered on trespasser avatar", + "Time based threshold, gradients centered on bottom of object", + "UNSUPPORTED" + ] + + description.text = descriptions[currentIndex] root.config["editedCategory"] = currentIndex; // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component // by setting the loader source to Null and then recreate it 100ms later paramWidgetLoader.sourceComponent = undefined; postpone.interval = 100 postpone.start() + root.sendToScript("category*"+currentIndex) } } + HifiControls.Button { + action: saveAction + Layout.fillWidth: true + anchors.top: parent.top + anchors.bottom: parent.bottom + } + HifiControls.Button { + action: loadAction + Layout.fillWidth: true + anchors.top: parent.top + anchors.bottom: parent.bottom + } + } + + HifiControls.Label { + id: description + text: "..." + Layout.fillWidth: true + wrapMode: Text.WordWrap } RowLayout { @@ -104,19 +147,18 @@ Rectangle { id: saveAction text: "Save" onTriggered: { - root.config.save() + fileDialog.title = "Save configuration..." + fileDialog.selectExisting = false + fileDialog.open() } } Action { id: loadAction text: "Load" onTriggered: { - root.config.load() - // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component - // by setting the loader source to Null and then recreate it 500ms later - paramWidgetLoader.sourceComponent = undefined; - postpone.interval = 500 - postpone.start() + fileDialog.title = "Load configuration..." + fileDialog.selectExisting = true + fileDialog.open() } } @@ -128,13 +170,8 @@ Rectangle { ColumnLayout { spacing: 3 width: root_col.width + property bool isTimeBased - HifiControls.CheckBox { - text: "Invert" - boxSize: 20 - checked: root.config["isInverted"] - onCheckedChanged: { root.config["isInverted"] = checked } - } RowLayout { Layout.fillWidth: true @@ -196,16 +233,32 @@ Rectangle { } } - - ConfigSlider { + RowLayout { + spacing: 20 height: 36 - label: "Edge Width" - integral: false - config: root.config - property: "edgeWidth" - max: 1.0 - min: 0.0 + + HifiControls.CheckBox { + text: "Invert gradient" + anchors.verticalCenter: parent.verticalCenter + boxSize: 20 + checked: root.config["isInverted"] + onCheckedChanged: { root.config["isInverted"] = checked } + } + ConfigSlider { + anchors.left: undefined + anchors.verticalCenter: parent.verticalCenter + height: 36 + width: 300 + label: "Edge Width" + integral: false + config: root.config + property: "edgeWidth" + max: 1.0 + min: 0.0 + } } + + RowLayout { Layout.fillWidth: true @@ -278,6 +331,8 @@ Rectangle { Layout.fillWidth: true ConfigSlider { + enabled: isTimeBased + opacity: isTimeBased ? 1.0 : 0.0 anchors.left: undefined anchors.right: undefined Layout.fillWidth: true @@ -290,6 +345,8 @@ Rectangle { min: 0.1 } HifiControls.ComboBox { + enabled: isTimeBased + opacity: isTimeBased ? 1.0 : 0.0 Layout.fillWidth: true model: ["Linear", "Ease In", "Ease Out", "Ease In / Out"] currentIndex: root.config["timing"] @@ -364,20 +421,6 @@ Rectangle { id: paramWidgetLoader sourceComponent: paramWidgets } - - Row { - anchors.left: parent.left - anchors.right: parent.right - - Button { - action: saveAction - } - Button { - action: loadAction - } - } - - } } \ No newline at end of file diff --git a/scripts/system/+android/audio.js b/scripts/system/+android/audio.js index b4f156d4bf..955f74d63a 100644 --- a/scripts/system/+android/audio.js +++ b/scripts/system/+android/audio.js @@ -46,7 +46,6 @@ function onMuteClicked() { printd("On Mute Clicked"); //Menu.setIsOptionChecked("Mute Microphone", !Menu.isOptionChecked("Mute Microphone")); Audio.muted = !Audio.muted; - onMuteToggled(); } function onMuteToggled() { diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js index 2b28fe2c9b..2946e541b5 100644 --- a/scripts/system/+android/avatarSelection.js +++ b/scripts/system/+android/avatarSelection.js @@ -35,6 +35,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'hide': Controller.setVPadHidden(false); + module.exports.hide(); module.exports.onHidden(); break; default: diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index 097798e393..c41ae1f327 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -33,7 +33,6 @@ function init() { radar.setUniqueColor(uniqueColor); radar.init(); setupModesBar(); - radar.isTouchValid = isRadarModeValidTouch; } function shutdown() { @@ -183,34 +182,6 @@ function onButtonClicked(clickedButton, whatToDo, hideAllAfter) { } } -function isRadarModeValidTouch(coords) { - var qmlFragments = [modesbar.qmlFragment]; - var windows = []; - for (var i=0; i < qmlFragments.length; i++) { - var aQmlFrag = qmlFragments[i]; - if (aQmlFrag != null && aQmlFrag.isVisible() && - coords.x >= aQmlFrag.position.x * 3 && coords.x <= aQmlFrag.position.x * 3 + aQmlFrag.size.x * 3 && - coords.y >= aQmlFrag.position.y * 3 && coords.y <= aQmlFrag.position.y * 3 + aQmlFrag.size.y * 3 - ) { - printd("godViewModeTouchValid- false because of qmlFragments!? idx " + i); - return false; - } - } - - for (var i=0; i < windows.length; i++) { - var aWin = windows[i]; - if (aWin != null && aWin.position() != null && - coords.x >= aWin.position().x * 3 && coords.x <= aWin.position().x * 3 + aWin.width() * 3 && - coords.y >= aWin.position().y * 3 && coords.y <= aWin.position().y * 3 + aWin.height() * 3 - ) { - printd("godViewModeTouchValid- false because of windows!?"); - return false; - } - } - printd("godViewModeTouchValid- true by default "); - return true; -} - Script.scriptEnding.connect(function () { shutdown(); }); diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 84fb66403f..455299dd5f 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -13,7 +13,7 @@ var radarModeInterface = {}; -var logEnabled = true; +var logEnabled = false; function printd(str) { if (logEnabled) { print("[radar.js] " + str); @@ -118,19 +118,10 @@ function actionOnObjectFromEvent(event) { } function mousePress(event) { - if (!isTouchValid(coords)) { - currentTouchIsValid = false; - return; - } else { - currentTouchIsValid = true; - } mousePressOrTouchEnd(event); } function mousePressOrTouchEnd(event) { - if (!currentTouchIsValid) { - return; - } if (radar) { if (actionOnObjectFromEvent(event)) { return; @@ -155,9 +146,6 @@ function fakeDoubleTap(event) { teleporter.dragTeleportRelease(event); } -var currentTouchIsValid = false; // Currently used to know if touch hasn't - // started on a UI overlay - var DOUBLE_TAP_TIME = 300; var fakeDoubleTapStart = Date.now(); var touchEndCount = 0; @@ -238,12 +226,6 @@ function touchEnd(event) { return; } - // if touch is invalid, cancel - if (!currentTouchIsValid) { - printd("touchEnd fail because !currentTouchIsValid"); - return; - } - if (analyzeDoubleTap(event)) return; // double tap detected, finish @@ -345,20 +327,6 @@ function computePointAtPlaneY(x, y, py) { p2.z, py); } -/******************************************************************************* - * - ******************************************************************************/ - -function isTouchValid(coords) { - // TODO: Extend to the detection of touches on new menu bars - var radarModeTouchValid = radarModeInterface.isTouchValid(coords); - - // getItemAtPoint does not exist anymore, look for another way to know if we - // are touching buttons - // is it still needed? - return /* !tablet.getItemAtPoint(coords) && */radarModeTouchValid; -} - /******************************************************************************* * ******************************************************************************/ @@ -373,16 +341,8 @@ function touchBegin(event) { x : event.x, y : event.y }; - if (!isTouchValid(coords)) { - printd("analyze touch - RADAR_TOUCH - INVALID"); - currentTouchIsValid = false; - touchStartingCoordinates = null; - } else { - printd("analyze touch - RADAR_TOUCH - ok"); - currentTouchIsValid = true; - touchStartingCoordinates = coords; - touchBeginTime = Date.now(); - } + touchStartingCoordinates = coords; + touchBeginTime = Date.now(); } var startedDraggingCamera = false; // first time @@ -848,9 +808,6 @@ function oneFingerTouchUpdate(event) { } function touchUpdate(event) { - if (!currentTouchIsValid) { - return; // avoid moving and zooming when tap is over UI entities - } if (event.isPinching || event.isPinchOpening) { pinchUpdate(event); } else { diff --git a/scripts/system/+android/stats.js b/scripts/system/+android/stats.js new file mode 100644 index 0000000000..a93bcb5794 --- /dev/null +++ b/scripts/system/+android/stats.js @@ -0,0 +1,39 @@ +"use strict"; +// +// stats.js +// scripts/system/ +// +// Created by Sam Gondelman on 3/14/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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var statsbar; +var statsButton; + +function init() { + statsbar = new QmlFragment({ + qml: "hifi/StatsBar.qml" + }); + + statsButton = statsbar.addButton({ + icon: "icons/stats.svg", + activeIcon: "icons/stats.svg", + textSize: 45, + bgOpacity: 0.0, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF", + text: "STATS" + }); + statsButton.clicked.connect(function() { + Menu.triggerOption("Stats"); + }); +} + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/assets/images/Overlay-Viz-blank.png b/scripts/system/assets/images/Overlay-Viz-blank.png index 76f535b6e6..bbbf44f7a9 100644 Binary files a/scripts/system/assets/images/Overlay-Viz-blank.png and b/scripts/system/assets/images/Overlay-Viz-blank.png differ diff --git a/scripts/system/away.js b/scripts/system/away.js index 2a45786d0d..dc9b33e952 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -24,8 +24,9 @@ var OVERLAY_HEIGHT = 1080; var OVERLAY_DATA = { width: OVERLAY_WIDTH, height: OVERLAY_HEIGHT, - imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", - color: {red: 255, green: 255, blue: 255}, + imageURL: Script.resolvePath("assets/images/Overlay-Viz-blank.png"), + emissive: true, + drawInFront: true, alpha: 1 }; var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away @@ -37,7 +38,7 @@ var OVERLAY_DATA_HMD = { localRotation: {x: 0, y: 0, z: 0, w: 1}, width: OVERLAY_WIDTH, height: OVERLAY_HEIGHT, - url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", + url: Script.resolvePath("assets/images/Overlay-Viz-blank.png"), color: {red: 255, green: 255, blue: 255}, alpha: 1, scale: 2 * MyAvatar.sensorToWorldScale, diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 994bde49eb..6ca624872e 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -18,8 +18,6 @@ var bubbleOverlayTimestamp; // Used for rate limiting the bubble sound var lastBubbleSoundTimestamp = 0; - // Used for flashing the HUD button upon activation - var bubbleButtonFlashState = false; // Affects bubble height var BUBBLE_HEIGHT_SCALE = 0.15; // The bubble model itself @@ -36,18 +34,16 @@ var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); // Is the update() function connected? var updateConnected = false; - var bubbleFlashTimer = false; var BUBBLE_VISIBLE_DURATION_MS = 3000; var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; - // Hides the bubble model overlay and resets the button flash state + // Hides the bubble model overlay function hideOverlays() { Overlays.editOverlay(bubbleOverlay, { visible: false }); - bubbleButtonFlashState = false; } // Make the bubble overlay visible, set its position, and play the sound @@ -89,14 +85,6 @@ bubbleOverlayTimestamp = nowTimestamp; Script.update.connect(update); updateConnected = true; - - // Flash button - if (!bubbleFlashTimer) { - bubbleFlashTimer = Script.setInterval(function () { - writeButtonProperties(bubbleButtonFlashState); - bubbleButtonFlashState = !bubbleButtonFlashState; - }, 500); - } } // Called from the C++ scripting interface to show the bubble overlay @@ -116,7 +104,6 @@ var delay = (timestamp - bubbleOverlayTimestamp); var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); if (overlayAlpha > 0) { - if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { Overlays.editOverlay(bubbleOverlay, { dimensions: { @@ -164,11 +151,6 @@ Script.update.disconnect(update); updateConnected = false; } - if (bubbleFlashTimer) { - Script.clearTimeout(bubbleFlashTimer); - bubbleFlashTimer = false; - } - writeButtonProperties(Users.getIgnoreRadiusEnabled()); } } @@ -176,10 +158,6 @@ // NOTE: the c++ calls this with just the first param -- we added a second // just for not logging the initial state of the bubble when we startup. function onBubbleToggled(enabled, doNotLog) { - if (bubbleFlashTimer) { - Script.clearTimeout(bubbleFlashTimer); - bubbleFlashTimer = false; - } writeButtonProperties(enabled); if (doNotLog !== true) { UserActivityLogger.bubbleToggled(enabled); @@ -214,17 +192,12 @@ // Cleanup the tablet button and overlays when script is stopped Script.scriptEnding.connect(function () { button.clicked.disconnect(Users.toggleIgnoreRadius); - if (bubbleFlashTimer) { - Script.clearTimeout(bubbleFlashTimer); - bubbleFlashTimer = false; - } if (tablet) { tablet.removeButton(button); } Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); Overlays.deleteOverlay(bubbleOverlay); - bubbleButtonFlashState = false; if (updateConnected === true) { Script.update.disconnect(update); } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index d2e7d3ffc8..9403a824e3 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -11,140 +11,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/*global XXX */ +/* global getConnectionData */ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); - var request = Script.require('request').request; + Script.include("/~/system/libraries/connectionUtils.js"); var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; - // Function Name: onButtonClicked() - // - // Description: - // -Fired when the app button is pressed. - // - // Relevant Variables: - // -WALLET_QML_SOURCE: The path to the Wallet QML - // -onWalletScreen: true/false depending on whether we're looking at the app. - var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; - var onWalletScreen = false; - function onButtonClicked() { - if (!tablet) { - print("Warning in buttonClicked(): 'tablet' undefined!"); - return; - } - if (onWalletScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(WALLET_QML_SOURCE); - } - } - // Function Name: sendToQml() - // - // Description: - // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to - // the QML in the format "{method, params}", like json-rpc. See also fromQml(). - function sendToQml(message) { - tablet.sendToQml(message); - } - - //*********************************************** - // - // BEGIN Connection logic - // - //*********************************************** - // Function Names: - // - requestJSON - // - getAvailableConnections - // - getInfoAboutUser - // - getConnectionData - // - // Description: - // - Update all the usernames that I am entitled to see, using my login but not dependent on canKick. - var METAVERSE_BASE = Account.metaverseServerURL; - function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise. - request({ - uri: url - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to get", url, error || response.status); - return; - } - callback(response.data); - }); - } - function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise) - url = METAVERSE_BASE + '/api/v1/users?per_page=400&' - if (domain) { - url += 'status=' + domain.slice(1, -1); // without curly braces - } else { - url += 'filter=connections'; // regardless of whether online - } - requestJSON(url, function (connectionsData) { - callback(connectionsData.users); - }); - } - function getInfoAboutUser(specificUsername, callback) { - url = METAVERSE_BASE + '/api/v1/users?filter=connections' - requestJSON(url, function (connectionsData) { - for (user in connectionsData.users) { - if (connectionsData.users[user].username === specificUsername) { - callback(connectionsData.users[user]); - return; - } - } - callback(false); - }); - } - function getConnectionData(specificUsername, domain) { - function frob(user) { // get into the right format - var formattedSessionId = user.location.node_id || ''; - if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) { - formattedSessionId = "{" + formattedSessionId + "}"; - } - return { - sessionId: formattedSessionId, - userName: user.username, - connection: user.connection, - profileUrl: user.images.thumbnail, - placeName: (user.location.root || user.location.domain || {}).name || '' - }; - } - if (specificUsername) { - getInfoAboutUser(specificUsername, function (user) { - if (user) { - updateUser(frob(user)); - } else { - print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); - } - }); - } else { - getAvailableConnections(domain, function (users) { - if (domain) { - users.forEach(function (user) { - updateUser(frob(user)); - }); - } else { - sendToQml({ method: 'updateConnections', connections: users.map(frob) }); - } - }); - } - } - //*********************************************** - // - // END Connection logic - // - //*********************************************** - - //*********************************************** - // - // BEGIN Avatar Selector logic - // - //*********************************************** + // BEGIN AVATAR SELECTOR LOGIC var UNSELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") @@ -415,7 +291,7 @@ userName: '' }; sendToQml(message); - + ExtendedOverlay.some(function (overlay) { var id = overlay.key; var selected = ExtendedOverlay.isSelected(id); @@ -521,11 +397,40 @@ triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); - //*********************************************** + // END AVATAR SELECTOR LOGIC + + // Function Name: onButtonClicked() // - // END Avatar Selector logic + // Description: + // -Fired when the app button is pressed. // - //*********************************************** + // Relevant Variables: + // -WALLET_QML_SOURCE: The path to the Wallet QML + // -onWalletScreen: true/false depending on whether we're looking at the app. + var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; + var onWalletScreen = false; + function onButtonClicked() { + if (!tablet) { + print("Warning in buttonClicked(): 'tablet' undefined!"); + return; + } + if (onWalletScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(WALLET_QML_SOURCE); + } + } + + // Function Name: sendToQml() + // + // Description: + // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to + // the QML in the format "{method, params}", like json-rpc. See also fromQml(). + function sendToQml(message) { + tablet.sendToQml(message); + } var sendMoneyRecipient; var sendMoneyParticleEffectUpdateTimer; @@ -678,18 +583,23 @@ } removeOverlays(); break; - case 'sendMoney_sendPublicly': - deleteSendMoneyParticleEffect(); - sendMoneyRecipient = message.recipient; - var amount = message.amount; - var props = SEND_MONEY_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - sendMoneyParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendMoneyParticleEffect(); - sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); + case 'sendAsset_sendPublicly': + if (message.assetName === "") { + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var amount = message.amount; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); + } break; case 'transactionHistory_goToBank': if (Account.metaverseServerURL.indexOf("staging") >= 0) { @@ -781,18 +691,19 @@ var isWired = false; var isUpdateOverlaysWired = false; function off() { - if (isWired) { // It is not ok to disconnect these twice, hence guard. + if (isWired) { Users.usernameFromIDReply.disconnect(usernameFromIDReply); Controller.mousePressEvent.disconnect(handleMouseEvent); Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + isWired = false; } if (isUpdateOverlaysWired) { Script.update.disconnect(updateOverlays); isUpdateOverlaysWired = false; } - triggerMapping.disable(); // It's ok if we disable twice. - triggerPressMapping.disable(); // see above removeOverlays(); } function shutdown() { diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 4f041d3067..4002fd297b 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -7,11 +7,12 @@ /* jslint bitwise: true */ -/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, +/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, COLORS_GRAB_SEARCHING_HALF_SQUEEZE - COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE, PointerManager + COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE, PointerManager, print + Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE */ controllerDispatcherPlugins = {}; @@ -123,6 +124,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return getControllerWorldLocation(Controller.Standard.RightHand, true); }; + Selection.enableListHighlight(DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE); + Selection.enableListToScene(DISPATCHER_HOVERING_LIST); + this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); @@ -157,7 +161,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.update = function () { try { _this.updateInternal(); - } catch (e) { + } catch (e) { print(e); } Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); @@ -477,6 +481,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); Pointers.removePointer(this.mouseRayPick); + Selection.disableListHighlight(DISPATCHER_HOVERING_LIST); }; } function mouseReleaseOnOverlay(overlayID, event) { diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 252f6efa9e..10bd5d28d3 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, - cloneEntity, DISPATCHER_PROPERTIES, TEAR_AWAY_DISTANCE, Uuid + cloneEntity, DISPATCHER_PROPERTIES, TEAR_AWAY_DISTANCE, Uuid, unhighlightTargetEntity */ Script.include("/~/system/libraries/Xform.js"); @@ -483,7 +483,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.dropGestureReset(); this.clearEquipHaptics(); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); var grabbedProperties = Entities.getEntityProperties(this.targetEntityID); // if an object is "equipped" and has a predefined offset, use it. diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 09cea58cea..cc884cdbc7 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -14,7 +14,7 @@ PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI - Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents + Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -103,6 +103,7 @@ Script.include("/~/system/libraries/Xform.js"); this.contextOverlayTimer = false; this.previousCollisionStatus = false; this.locked = false; + this.highlightedEntity = null; this.reticleMinX = MARGIN; this.reticleMaxX; this.reticleMinY = MARGIN; @@ -295,6 +296,7 @@ Script.include("/~/system/libraries/Xform.js"); this.actionID = null; this.grabbedThingID = null; this.targetObject = null; + this.potentialEntityWithContextOverlay = false; }; this.updateRecommendedArea = function() { @@ -401,6 +403,9 @@ Script.include("/~/system/libraries/Xform.js"); if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.notPointingAtEntity(controllerData) || this.targetIsNull()) { this.endNearGrabAction(); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; return makeRunningValues(false, [], []); } this.intersectionDistance = controllerData.rayPicks[this.hand].distance; @@ -449,7 +454,9 @@ Script.include("/~/system/libraries/Xform.js"); if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { if (controllerData.triggerClicks[this.hand]) { var entityID = rayPickInfo.objectID; - + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; var targetProps = Entities.getEntityProperties(entityID, [ "dynamic", "shapeType", "position", "rotation", "dimensions", "density", @@ -467,15 +474,17 @@ Script.include("/~/system/libraries/Xform.js"); Script.clearTimeout(this.contextOverlayTimer); } this.contextOverlayTimer = false; - if (entityID !== this.entityWithContextOverlay) { + if (entityID === this.entityWithContextOverlay) { this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } var targetEntity = this.targetObject.getTargetEntity(); entityID = targetEntity.id; targetProps = targetEntity.props; - if (entityIsGrabbable(targetProps)) { + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { if (!entityIsDistanceGrabbable(targetProps)) { this.targetObject.makeDynamic(); } @@ -495,38 +504,62 @@ Script.include("/~/system/libraries/Xform.js"); this.startFarGrabAction(controllerData, targetProps); } } - } else if (!this.entityWithContextOverlay) { - var _this = this; + } else { + var targetEntityID = rayPickInfo.objectID; + if (this.highlightedEntity !== targetEntityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + var selectionTargetProps = Entities.getEntityProperties(targetEntityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); - if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { - if (_this.contextOverlayTimer) { - Script.clearTimeout(_this.contextOverlayTimer); + var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); + selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); + var selectionTargetEntity = selectionTargetObject.getTargetEntity(); + + if (entityIsGrabbable(selectionTargetEntity.props) || + entityIsGrabbable(selectionTargetObject.entityProps)) { + + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); } - _this.contextOverlayTimer = false; - _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + this.highlightedEntity = rayPickInfo.objectID; } - if (!_this.contextOverlayTimer) { - _this.contextOverlayTimer = Script.setTimeout(function () { - if (!_this.entityWithContextOverlay && - _this.contextOverlayTimer && - _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { - var props = Entities.getEntityProperties(rayPickInfo.objectID); - var pointerEvent = { - type: "Move", - id: _this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, rayPickInfo.intersection, props), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.surfaceNormal, - direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { - _this.entityWithContextOverlay = rayPickInfo.objectID; - } + if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); } _this.contextOverlayTimer = false; - }, 500); + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var props = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, rayPickInfo.intersection, props), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } } } } else if (this.distanceRotating) { @@ -542,6 +575,9 @@ Script.include("/~/system/libraries/Xform.js"); if (disableModule) { if (disableModule.disableModules) { this.endNearGrabAction(); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; return makeRunningValues(false, [], []); } } diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js new file mode 100644 index 0000000000..a66f7bd144 --- /dev/null +++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js @@ -0,0 +1,148 @@ +// +// highlightNearbyEntities.js +// +// Created by Dante Ruiz 2018-4-10 +// 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 + + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, MyAvatar, getGrabPointSphereOffset, + makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters, + PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + DEFAULT_SEARCH_SPHERE_DISTANCE, getGrabbableData, makeLaserParams, entityIsCloneable, Messages, print +*/ + +"use strict"; + +(function () { + Script.include("/~/system/libraries/controllerDispatcherUtils.js"); + Script.include("/~/system/libraries/controllers.js"); + Script.include("/~/system/libraries/cloneEntityUtils.js"); + var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); + + function differenceInArrays(firstArray, secondArray) { + var differenceArray = firstArray.filter(function(element) { + return secondArray.indexOf(element) < 0; + }); + + return differenceArray; + } + + function HighlightNearbyEntities(hand) { + this.hand = hand; + this.otherHand = hand === dispatcherUtils.RIGHT_HAND ? dispatcherUtils.LEFT_HAND : + dispatcherUtils.RIGHT_HAND; + this.highlightedEntities = []; + + this.parameters = dispatcherUtils.makeDispatcherModuleParameters( + 480, + this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + + this.isGrabable = function(controllerData, props) { + var canGrabEntity = false; + if (dispatcherUtils.entityIsGrabbable(props) || entityIsCloneable(props)) { + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = dispatcherUtils.findGroupParent(controllerData, props); + canGrabEntity = true; + if (!dispatcherUtils.entityIsGrabbable(groupRootProps)) { + canGrabEntity = false; + } + } + return canGrabEntity; + }; + + this.hasHyperLink = function(props) { + return (props.href !== "" && props.href !== undefined); + }; + + this.removeEntityFromHighlightList = function(entityID) { + var index = this.highlightedEntities.indexOf(entityID); + if (index > -1) { + this.highlightedEntities.splice(index, 1); + } + }; + + this.getOtherModule = function() { + var otherModule = this.hand === dispatcherUtils.RIGHT_HAND ? leftHighlightNearbyEntities : + rightHighlightNearbyEntities; + return otherModule; + }; + + this.getOtherHandHighlightedEntities = function() { + return this.getOtherModule().highlightedEntities; + }; + + this.highlightEntities = function(controllerData) { + var nearbyEntitiesProperties = controllerData.nearbyEntityProperties[this.hand]; + var otherHandHighlightedEntities = this.getOtherHandHighlightedEntities(); + var newHighlightedEntities = []; + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + for (var i = 0; i < nearbyEntitiesProperties.length; i++) { + var props = nearbyEntitiesProperties[i]; + if (props.distance > dispatcherUtils.NEAR_GRAB_RADIUS * sensorScaleFactor) { + continue; + } + if (this.isGrabable(controllerData, props) || this.hasHyperLink(props)) { + dispatcherUtils.highlightTargetEntity(props.id); + newHighlightedEntities.push(props.id); + } + } + + var unhighlightEntities = differenceInArrays(this.highlightedEntities, newHighlightedEntities); + + unhighlightEntities.forEach(function(entityID) { + if (otherHandHighlightedEntities.indexOf(entityID) < 0 ) { + dispatcherUtils.unhighlightTargetEntity(entityID); + } + }); + this.highlightedEntities = newHighlightedEntities; + }; + + this.isReady = function(controllerData) { + this.highlightEntities(controllerData); + return dispatcherUtils.makeRunningValues(false, [], []); + }; + + this.run = function(controllerData) { + return this.isReady(controllerData); + }; + } + + var handleMessage = function(channel, message, sender) { + var data; + if (sender === MyAvatar.sessionUUID) { + if (channel === 'Hifi-unhighlight-entity') { + try { + data = JSON.parse(message); + + var hand = data.hand; + if (hand === dispatcherUtils.LEFT_HAND) { + leftHighlightNearbyEntities.removeEntityFromHighlightList(data.entityID); + } else if (hand === dispatcherUtils.RIGHT_HAND) { + rightHighlightNearbyEntities.removeEntityFromHighlightList(data.entityID); + } + } catch (e) { + print("Failed to parse message"); + } + } + } + }; + var leftHighlightNearbyEntities = new HighlightNearbyEntities(dispatcherUtils.LEFT_HAND); + var rightHighlightNearbyEntities = new HighlightNearbyEntities(dispatcherUtils.RIGHT_HAND); + + dispatcherUtils.enableDispatcherModule("LeftHighlightNearbyEntities", leftHighlightNearbyEntities); + dispatcherUtils.enableDispatcherModule("RightHighlightNearbyEntities", rightHighlightNearbyEntities); + + function cleanup() { + dispatcherUtils.disableDispatcherModule("LeftHighlightNearbyEntities"); + dispatcherUtils.disableDispatcherModule("RightHighlightNearbyEntities"); + } + Messages.subscribe('Hifi-unhighlight-entity'); + Messages.messageReceived.connect(handleMessage); + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/mouseHighlightEntities.js b/scripts/system/controllers/controllerModules/mouseHighlightEntities.js new file mode 100644 index 0000000000..6af1055a69 --- /dev/null +++ b/scripts/system/controllers/controllerModules/mouseHighlightEntities.js @@ -0,0 +1,77 @@ +// +// mouseHighlightEntities.js +// +// scripts/system/controllers/controllerModules/ +// +// Created by Dante Ruiz 2018-4-11 +// 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 +// + +/* jslint bitwise: true */ + +/* global Script, print, Entities, Picks, HMD */ + + +(function() { + var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); + + function MouseHighlightEntities() { + this.highlightedEntity = null; + + this.parameters = dispatcherUtils.makeDispatcherModuleParameters( + 5, + ["mouse"], + [], + 100); + + this.isReady = function(controllerData) { + if (HMD.active) { + if (this.highlightedEntity) { + dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity); + this.highlightedEntity = null; + } + } else { + var pickResult = controllerData.mouseRayPick; + if (pickResult.type === Picks.INTERSECTED_ENTITY) { + var targetEntityID = pickResult.objectID; + + if (this.highlightedEntity !== targetEntityID) { + var targetProps = Entities.getEntityProperties(targetEntityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + + if (this.highlightedEntity) { + dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity); + this.highlightedEntity = null; + } + + if (dispatcherUtils.entityIsGrabbable(targetProps)) { + // highlight entity + dispatcherUtils.highlightTargetEntity(targetEntityID); + this.highlightedEntity = targetEntityID; + } + } + } + } + + return dispatcherUtils.makeRunningValues(false, [], []); + }; + + this.run = function(controllerData) { + return this.isReady(controllerData); + }; + } + + var mouseHighlightEntities = new MouseHighlightEntities(); + dispatcherUtils.enableDispatcherModule("MouseHighlightEntities", mouseHighlightEntities); + + function cleanup() { + dispatcherUtils.disableDispatcherModule("MouseHighlightEntities"); + } + Script.scriptEnding.connect(cleanup); +})(); diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 147d6b807f..a4e439fe2f 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -10,7 +10,7 @@ propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, - HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -114,6 +114,13 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "startNearGrab", args); + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); }; // this is for when the action is going to time-out diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 01c8424e0c..d454d20a02 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -11,7 +11,8 @@ TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME, - TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox + TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Selection, DISPATCHER_HOVERING_LIST, Uuid, + highlightTargetEntity, unhighlightTargetEntity */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -34,6 +35,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.autoUnequipCounter = 0; this.lastUnexpectedChildrenCheckTime = 0; this.robbed = false; + this.highlightedEntity = null; this.parameters = makeDispatcherModuleParameters( 500, @@ -87,7 +89,13 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.startNearParentingGrabEntity = function (controllerData, targetProps) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); var handJointIndex; // if (this.ignoreIK) { // handJointIndex = this.controllerJointIndex; @@ -158,6 +166,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); grabbedEntity: this.targetEntityID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); + unhighlightTargetEntity(this.targetEntityID); this.grabbing = false; this.targetEntityID = null; this.robbed = false; @@ -280,6 +289,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it } else { this.targetEntityID = targetProps.id; + this.highlightedEntity = this.targetEntityID; + highlightTargetEntity(this.targetEntityID); return makeRunningValues(true, [this.targetEntityID], []); } } else { @@ -300,6 +311,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; if (!props) { // entity was deleted + unhighlightTargetEntity(this.targetEntityID); this.grabbing = false; this.targetEntityID = null; this.hapticTargetID = null; @@ -321,6 +333,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var readiness = this.isReady(controllerData); if (!readiness.active) { this.robbed = false; + unhighlightTargetEntity(this.highlightedEntity); return readiness; } if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 42db3d6f61..6a9cd9fbcd 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -7,7 +7,7 @@ /* global Script, Entities, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, getGrabbableData, - Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS + Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS, unhighlightTargetEntity */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -55,6 +55,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.startNearTrigger = function (controllerData) { var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "startNearTrigger", args); + unhighlightTargetEntity(this.targetEntityID); }; this.continueNearTrigger = function (controllerData) { diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 8db8e29f37..50e627fe5d 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -32,7 +32,9 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/hudOverlayPointer.js", "controllerModules/mouseHMD.js", "controllerModules/scaleEntity.js", - "controllerModules/nearGrabHyperLinkEntity.js" + "controllerModules/highlightNearbyEntities.js", + "controllerModules/nearGrabHyperLinkEntity.js", + "controllerModules/mouseHighlightEntities.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 1171703847..36dd9314bb 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -8,14 +8,14 @@ // // Grab's physically moveable entities with the mouse, by applying a spring force. // -// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect +// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /* global MyAvatar, Entities, Script, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller, - isInEditMode, HMD entityIsGrabbable, Picks, PickType, Pointers*/ + isInEditMode, HMD entityIsGrabbable, Picks, PickType, Pointers, unhighlightTargetEntity*/ (function() { // BEGIN LOCAL_SCOPE @@ -354,6 +354,7 @@ Grabber.prototype.pressEvent = function(event) { Pointers.setRenderState(this.mouseRayEntities, "grabbed"); Pointers.setLockEndUUID(this.mouseRayEntities, pickResults.objectID, false); + unhighlightTargetEntity(pickResults.objectID); mouse.startDrag(event); @@ -444,6 +445,7 @@ Grabber.prototype.releaseEvent = function(event) { this.actionID = null; Pointers.setRenderState(this.mouseRayEntities, ""); + Pointers.setLockEndUUID(this.mouseRayEntities, null, false); var args = "mouse"; Entities.callEntityMethod(this.entityID, "releaseGrab", args); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index cfaf517487..6da3592999 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -116,11 +116,19 @@ var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; +var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; +var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; +var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; +var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; +var SETTING_EDIT_PREFIX = "Edit/"; + + var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; @@ -226,7 +234,7 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; -var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; + var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts @@ -280,7 +288,7 @@ var toolBar = (function () { position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); properties.position = position; - if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM) && + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } }); } else { @@ -338,7 +346,6 @@ var toolBar = (function () { if (systemToolbar) { systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); } - Menu.removeMenuItem(GRABBABLE_ENTITIES_MENU_CATEGORY, GRABBABLE_ENTITIES_MENU_ITEM); } var buttonHandlers = {}; // only used to tablet mode @@ -409,6 +416,12 @@ var toolBar = (function () { // default: // shapeType = "uv"; //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }) + } var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; if (materialURL) { @@ -416,7 +429,8 @@ var toolBar = (function () { type: "Material", materialURL: materialURL, //materialMappingMode: materialMappingMode, - priority: DEFAULT_LAYERED_MATERIAL_PRIORITY + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData }); } } @@ -438,19 +452,48 @@ var toolBar = (function () { } } + // Handles any edit mode updates required when domains have switched + function checkEditPermissionsAndUpdate() { + if ((createButton === null) || (createButton === undefined)) { + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), + }); + + if (!hasRezPermissions && isActive) { + that.setActive(false); + tablet.gotoHomeScreen(); + } + } + function initialize() { Script.scriptEnding.connect(cleanup); Window.domainChanged.connect(function () { + if (isActive) { + tablet.gotoHomeScreen(); + } that.setActive(false); that.clearEntityList(); + checkEditPermissionsAndUpdate(); }); Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { if (isActive && !canAdjustLocks) { that.setActive(false); } + checkEditPermissionsAndUpdate(); }); + Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -836,37 +879,18 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { } } -// Handles any edit mode updates required when domains have switched -function handleDomainChange() { - if ( (createButton === null) || (createButton === undefined) ){ - //--EARLY EXIT--( nothing to safely update ) - return; - } - - var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); - createButton.editProperties({ - icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), - captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), - }); -} - function handleMessagesReceived(channel, message, sender) { switch( channel ){ case 'entityToolUpdates': { handleOverlaySelectionToolUpdates( channel, message, sender ); break; } - case 'Toolbar-DomainChanged': { - handleDomainChange(); - break; - } default: { return; } } } -Messages.subscribe('Toolbar-DomainChanged'); Messages.subscribe("entityToolUpdates"); Messages.messageReceived.connect(handleMessagesReceived); @@ -1143,34 +1167,35 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: GRABBABLE_ENTITIES_MENU_CATEGORY, - menuItemName: GRABBABLE_ENTITIES_MENU_ITEM, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, afterItem: "Unparent Entity", isCheckable: true, - isChecked: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, true), grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", - menuItemName: "Allow Selecting of Large Models", - afterItem: GRABBABLE_ENTITIES_MENU_ITEM, + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, isCheckable: true, - isChecked: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true), grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", - menuItemName: "Allow Selecting of Small Models", - afterItem: "Allow Selecting of Large Models", + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, isCheckable: true, - isChecked: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true), grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", - menuItemName: "Allow Selecting of Lights", - afterItem: "Allow Selecting of Small Models", + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false), grouping: "Advanced" }); Menu.addMenuItem({ @@ -1275,6 +1300,12 @@ Script.scriptEnding.connect(function () { Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)); + + progressDialog.cleanup(); cleanupModelMenus(); tooltip.cleanup(); @@ -1293,8 +1324,6 @@ Script.scriptEnding.connect(function () { Messages.messageReceived.disconnect(handleMessagesReceived); Messages.unsubscribe("entityToolUpdates"); - // Messages.unsubscribe("Toolbar-DomainChanged"); // Do not unsubscribe because the shapes.js app also subscribes and - // Messages.subscribe works script engine-wide which would mess things up if they're both run in the same engine. createButton = null; }); @@ -2047,7 +2076,7 @@ var PropertiesTool = function (opts) { parentSelectedEntities(); } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if (data.type === 'saveUserData') { + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; Entities.editEntity(actualID, data.properties); @@ -2310,6 +2339,11 @@ var PopupMenu = function () { Controller.mousePressEvent.disconnect(self.mousePressEvent); Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + + Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); } Controller.mousePressEvent.connect(self.mousePressEvent); diff --git a/scripts/system/help.js b/scripts/system/help.js index e29fc59e59..aaeb82721c 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -40,7 +40,7 @@ } function onScreenChanged(type, url) { - onHelpScreen = type === "Web" && url.startsWith(HELP_URL); + onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0); button.editProperties({ isActive: onHelpScreen }); } diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 7664ae8714..6c1931932a 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1387,12 +1387,14 @@ input#reset-to-natural-dimensions { margin-top: 48px; } -#userdata-clear{ +#userdata-clear, +#materialdata-clear { margin-bottom: 10px; } -#static-userdata { +#static-userdata, +#static-materialData { display: none; z-index: 99; position: absolute; @@ -1403,7 +1405,8 @@ input#reset-to-natural-dimensions { background-color: #2e2e2e; } -#userdata-saved { +#userdata-saved, +#materialData-saved { margin-top:5px; font-size:16px; display:none; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 82a450bedd..8647dca035 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -781,6 +781,20 @@ + +
+ +

+
+ + + + Saved! +
+
+
+ +
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 5ea1dd0963..0c3e6199f3 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -677,7 +677,6 @@ window.onload = function () { shareForUrl("p1"); appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast); document.getElementById("p1").classList.remove("processingGif"); - document.getElementById("snap-button").disabled = false; } } else { imageCount = message.image_data.length; @@ -685,6 +684,7 @@ window.onload = function () { addImage(element, messageOptions.isLoggedIn, messageOptions.canShare, false, false, false, false, true); }); } + document.getElementById("snap-button").disabled = false; break; case 'captureSettings': handleCaptureSetting(message.setting); @@ -728,9 +728,7 @@ function takeSnapshot() { type: "snapshot", action: "takeSnapshot" })); - if (document.getElementById('stillAndGif').checked === true) { - document.getElementById("snap-button").disabled = true; - } + document.getElementById("snap-button").disabled = true; } function isPrintDisabled() { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d2fa2eeeb0..4b6329db44 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -66,6 +66,7 @@ function enableProperties() { if (elLocked.checked === false) { removeStaticUserData(); + removeStaticMaterialData(); } } @@ -78,8 +79,13 @@ function disableProperties() { } var elLocked = document.getElementById("property-locked"); - if ($('#userdata-editor').css('display') === "block" && elLocked.checked === true) { - showStaticUserData(); + if (elLocked.checked === true) { + if ($('#userdata-editor').css('display') === "block") { + showStaticUserData(); + } + if ($('#materialdata-editor').css('display') === "block") { + showStaticMaterialData(); + } } } @@ -356,15 +362,139 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal multiDataUpdater(groupName, val, userDataElement, def); } +function setMaterialDataFromEditor(noUpdate) { + var json = null; + try { + json = materialEditor.get(); + } catch (e) { + alert('Invalid JSON code - look for red X in your code ', +e); + } + if (json === null) { + return; + } else { + var text = materialEditor.getText(); + if (noUpdate === true) { + EventBridge.emitWebEvent( + JSON.stringify({ + id: lastEntityID, + type: "saveMaterialData", + properties: { + materialData: text + } + }) + ); + return; + } else { + updateProperty('materialData', text); + } + } +} + function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); } +var materialEditor = null; + +function createJSONMaterialEditor() { + var container = document.getElementById("materialdata-editor"); + var options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'materialData', + onModeChange: function() { + $('.jsoneditor-poweredBy').remove(); + }, + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + var currentJSONString = materialEditor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#materialdata-save').attr('disabled', false); + + + } + }; + materialEditor = new JSONEditor(container, options); +} + +function hideNewJSONMaterialEditorButton() { + $('#materialdata-new-editor').hide(); +} + +function showSaveMaterialDataButton() { + $('#materialdata-save').show(); +} + +function hideSaveMaterialDataButton() { + $('#materialdata-save').hide(); +} + +function showNewJSONMaterialEditorButton() { + $('#materialdata-new-editor').show(); +} + +function showMaterialDataTextArea() { + $('#property-material-data').show(); +} + +function hideMaterialDataTextArea() { + $('#property-material-data').hide(); +} + +function showStaticMaterialData() { + if (materialEditor !== null) { + $('#static-materialdata').show(); + $('#static-materialdata').css('height', $('#materialdata-editor').height()); + $('#static-materialdata').text(materialEditor.getText()); + } +} + +function removeStaticMaterialData() { + $('#static-materialdata').hide(); +} + +function setMaterialEditorJSON(json) { + materialEditor.set(json); + if (materialEditor.hasOwnProperty('expandAll')) { + materialEditor.expandAll(); + } +} + +function getMaterialEditorJSON() { + return materialEditor.get(); +} + +function deleteJSONMaterialEditor() { + if (materialEditor !== null) { + materialEditor.destroy(); + materialEditor = null; + } +} + +var savedMaterialJSONTimer = null; + +function saveJSONMaterialData(noUpdate) { + setMaterialDataFromEditor(noUpdate); + $('#materialdata-saved').show(); + $('#materialdata-save').attr('disabled', true); + if (savedMaterialJSONTimer !== null) { + clearTimeout(savedMaterialJSONTimer); + } + savedMaterialJSONTimer = setTimeout(function() { + $('#materialdata-saved').hide(); + + }, EDITOR_TIMEOUT_DURATION); +} + var editor = null; -var editorTimeout = null; -var lastJSONString = null; function createJSONEditor() { var container = document.getElementById("userdata-editor"); @@ -395,11 +525,6 @@ function createJSONEditor() { function hideNewJSONEditorButton() { $('#userdata-new-editor').hide(); - -} - -function hideClearUserDataButton() { - $('#userdata-clear').hide(); } function showSaveUserDataButton() { @@ -408,17 +533,10 @@ function showSaveUserDataButton() { function hideSaveUserDataButton() { $('#userdata-save').hide(); - } function showNewJSONEditorButton() { $('#userdata-new-editor').show(); - -} - -function showClearUserDataButton() { - $('#userdata-clear').show(); - } function showUserDataTextArea() { @@ -446,7 +564,6 @@ function setEditorJSON(json) { if (editor.hasOwnProperty('expandAll')) { editor.expandAll(); } - } function getEditorJSON() { @@ -484,12 +601,15 @@ function bindAllNonJSONEditorElements() { // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear") { + if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear" || e.target.id === "materialdata-new-editor" || e.target.id === "materialdata-clear") { return; } else { if ($('#userdata-editor').css('height') !== "0px") { saveJSONUserData(true); } + if ($('#materialdata-editor').css('height') !== "0px") { + saveJSONMaterialData(true); + } } }); } @@ -652,6 +772,10 @@ function loaded() { var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x"); var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y"); var elMaterialMappingRot = document.getElementById("property-material-mapping-rot"); + var elMaterialData = document.getElementById("property-material-data"); + var elClearMaterialData = document.getElementById("materialdata-clear"); + var elSaveMaterialData = document.getElementById("materialdata-save"); + var elNewJSONMaterialEditor = document.getElementById('materialdata-new-editor'); var elImageURL = document.getElementById("property-image-url"); @@ -772,9 +896,15 @@ function loaded() { } else if (data.type === "update") { if (!data.selections || data.selections.length === 0) { - if (editor !== null && lastEntityID !== null) { - saveJSONUserData(true); - deleteJSONEditor(); + if (lastEntityID !== null) { + if (editor !== null) { + saveJSONUserData(true); + deleteJSONEditor(); + } + if (materialEditor !== null) { + saveJSONMaterialData(true); + deleteJSONMaterialEditor(); + } } elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; @@ -783,6 +913,7 @@ function loaded() { disableProperties(); } else if (data.selections && data.selections.length > 1) { deleteJSONEditor(); + deleteJSONMaterialEditor(); var selections = data.selections; var ids = []; @@ -815,8 +946,13 @@ function loaded() { } else { properties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { - saveJSONUserData(true); + if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + if (editor !== null) { + saveJSONUserData(true); + } + if (materialEditor !== null) { + saveJSONMaterialData(true); + } } var doSelectElement = lastEntityID === '"' + properties.id + '"'; @@ -993,6 +1129,28 @@ function loaded() { hideNewJSONEditorButton(); } + var materialJson = null; + try { + materialJson = JSON.parse(properties.materialData); + } catch (e) { + // normal text + deleteJSONMaterialEditor(); + elMaterialData.value = properties.materialData; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + } + if (materialJson !== null) { + if (materialEditor === null) { + createJSONMaterialEditor(); + } + + setMaterialEditorJSON(materialJson); + showSaveMaterialDataButton(); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + } + elHyperlinkHref.value = properties.href; elDescription.value = properties.description; @@ -1200,6 +1358,7 @@ function loaded() { } else { enableProperties(); elSaveUserData.disabled = true; + elSaveMaterialData.disabled = true; } var activeElement = document.activeElement; @@ -1384,6 +1543,31 @@ function loaded() { showSaveUserDataButton(); }); + elClearMaterialData.addEventListener("click", function() { + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value); + }); + + elSaveMaterialData.addEventListener("click", function() { + saveJSONMaterialData(true); + }); + + elMaterialData.addEventListener('change', createEmitTextPropertyUpdateFunction('materialData')); + + elNewJSONMaterialEditor.addEventListener('click', function() { + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + var data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); + }); + var colorChangeFunction = createEmitColorPropertyUpdateFunction( 'color', elColorRed, elColorGreen, elColorBlue); elColorRed.addEventListener('change', colorChangeFunction); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 511bb6989e..a7186f55bd 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -307,6 +307,10 @@ WebTablet.prototype.setScriptURL = function (scriptURL) { Overlays.editOverlay(this.webOverlayID, { scriptURL: scriptURL }); }; +WebTablet.prototype.getOverlayObject = function () { + return Overlays.getOverlayObject(this.webOverlayID); +}; + WebTablet.prototype.setWidth = function (width) { // imported from libraries/utils.js resizeTablet(width); diff --git a/scripts/system/libraries/connectionUtils.js b/scripts/system/libraries/connectionUtils.js new file mode 100644 index 0000000000..166d379330 --- /dev/null +++ b/scripts/system/libraries/connectionUtils.js @@ -0,0 +1,94 @@ +// +// connectionUtils.js +// scripts/system/libraries +// +// Copyright 2018 High Fidelity, Inc. +// + +// Function Names: +// - requestJSON +// - getAvailableConnections +// - getInfoAboutUser +// - getConnectionData +// +// Description: +// - Update all the usernames that I am entitled to see, using my login but not dependent on canKick. +var request = Script.require('request').request; +var METAVERSE_BASE = Account.metaverseServerURL; +function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise. + request({ + uri: url + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("Error: unable to get", url, error || response.status); + return; + } + callback(response.data); + }); +} +function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise) + url = METAVERSE_BASE + '/api/v1/users?per_page=400&' + if (domain) { + url += 'status=' + domain.slice(1, -1); // without curly braces + } else { + url += 'filter=connections'; // regardless of whether online + } + requestJSON(url, function (connectionsData) { + callback(connectionsData.users); + }); +} +function getInfoAboutUser(specificUsername, callback) { + url = METAVERSE_BASE + '/api/v1/users?filter=connections' + requestJSON(url, function (connectionsData) { + for (user in connectionsData.users) { + if (connectionsData.users[user].username === specificUsername) { + callback(connectionsData.users[user]); + return; + } + } + callback(false); + }); +} +getConnectionData = function getConnectionData(specificUsername, domain) { + function frob(user) { // get into the right format + var formattedSessionId = user.location.node_id || ''; + if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) { + formattedSessionId = "{" + formattedSessionId + "}"; + } + return { + sessionId: formattedSessionId, + userName: user.username, + connection: user.connection, + profileUrl: user.images.thumbnail, + placeName: (user.location.root || user.location.domain || {}).name || '' + }; + } + if (specificUsername) { + getInfoAboutUser(specificUsername, function (user) { + if (user) { + updateUser(frob(user)); + } else { + print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); + } + }); + } else { + getAvailableConnections(domain, function (users) { + if (domain) { + users.forEach(function (user) { + updateUser(frob(user)); + }); + } else { + sendToQml({ method: 'updateConnections', connections: users.map(frob) }); + } + }); + } +} + +// Function Name: sendToQml() +// +// Description: +// -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to +// the QML in the format "{method, params}", like json-rpc. See also fromQml(). +function sendToQml(message) { + Tablet.getTablet("com.highfidelity.interface.tablet.system").sendToQml(message); +} diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 75e1d6668b..9edd6d006a 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -7,6 +7,7 @@ /* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, + Selection, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true, HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true, @@ -22,6 +23,8 @@ DISPATCHER_PROPERTIES:true, HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, + DISPATCHER_HOVERING_LIST:true, + DISPATCHER_HOVERING_STYLE:true, Entities, makeDispatcherModuleParameters:true, makeRunningValues:true, @@ -49,7 +52,10 @@ TEAR_AWAY_DISTANCE:true, TEAR_AWAY_COUNT:true, TEAR_AWAY_CHECK_TIME:true, - distanceBetweenPointAndEntityBoundingBox:true + distanceBetweenPointAndEntityBoundingBox:true, + highlightTargetEntity:true, + clearHighlightedEntities:true, + unhighlightTargetEntity:true */ MSECS_PER_SEC = 1000.0; @@ -88,6 +94,19 @@ NEAR_GRAB_RADIUS = 1.0; TEAR_AWAY_DISTANCE = 0.1; // ungrab an entity if its bounding-box moves this far from the hand TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks +DISPATCHER_HOVERING_LIST = "dispactherHoveringList"; +DISPATCHER_HOVERING_STYLE = { + isOutlineSmooth: true, + outlineWidth: 0, + outlineUnoccludedColor: {red: 255, green: 128, blue: 128}, + outlineUnoccludedAlpha: 0.0, + outlineOccludedColor: {red: 255, green: 128, blue: 128}, + outlineOccludedAlpha:0.0, + fillUnoccludedColor: {red: 255, green: 255, blue: 255}, + fillUnoccludedAlpha: 0.12, + fillOccludedColor: {red: 255, green: 255, blue: 255}, + fillOccludedAlpha: 0.0 +}; DISPATCHER_PROPERTIES = [ "position", @@ -220,6 +239,18 @@ entityIsGrabbable = function (props) { return true; }; +clearHighlightedEntities = function() { + Selection.clearSelectedItemsList(DISPATCHER_HOVERING_LIST); +}; + +highlightTargetEntity = function(entityID) { + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID); +}; + +unhighlightTargetEntity = function(entityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID); +}; + entityIsDistanceGrabbable = function(props) { if (!entityIsGrabbable(props)) { return false; @@ -389,7 +420,11 @@ if (typeof module !== 'undefined') { makeDispatcherModuleParameters: makeDispatcherModuleParameters, enableDispatcherModule: enableDispatcherModule, disableDispatcherModule: disableDispatcherModule, + highlightTargetEntity: highlightTargetEntity, + unhighlightTargetEntity: unhighlightTargetEntity, + clearHighlightedEntities: clearHighlightedEntities, makeRunningValues: makeRunningValues, + findGroupParent: findGroupParent, LEFT_HAND: LEFT_HAND, RIGHT_HAND: RIGHT_HAND, BUMPER_ON_VALUE: BUMPER_ON_VALUE, @@ -400,6 +435,7 @@ if (typeof module !== 'undefined') { projectOntoOverlayXYPlane: projectOntoOverlayXYPlane, projectOntoEntityXYPlane: projectOntoEntityXYPlane, TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE, - TRIGGER_ON_VALUE: TRIGGER_ON_VALUE + TRIGGER_ON_VALUE: TRIGGER_ON_VALUE, + DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST }; } diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index fced5fc4e9..2322210522 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -13,9 +13,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global HIFI_PUBLIC_BUCKET, SPACE_LOCAL, Script, SelectionManager */ - -HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +/* global SPACE_LOCAL, SelectionManager */ SPACE_LOCAL = "local"; SPACE_WORLD = "world"; @@ -50,6 +48,7 @@ SelectionManager = (function() { messageParsed = JSON.parse(message); } catch (err) { print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message: " + message); + return; } if (messageParsed.method === "selectEntity") { @@ -155,6 +154,20 @@ SelectionManager = (function() { that._update(true); }; + that.duplicateSelection = function() { + var duplicatedEntityIDs = []; + Object.keys(that.savedProperties).forEach(function(otherEntityID) { + var properties = that.savedProperties[otherEntityID]; + if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { + duplicatedEntityIDs.push({ + entityID: Entities.addEntity(properties), + properties: properties + }); + } + }); + return duplicatedEntityIDs; + } + that._update = function(selectionUpdated) { var properties = null; if (that.selections.length === 0) { @@ -443,7 +456,7 @@ SelectionDisplay = (function() { solid: true, visible: false, ignoreRayIntersection: true, - drawInFront: true, + drawInFront: true } var handleStretchXPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); Overlays.editOverlay(handleStretchXPanel, { color : COLOR_RED }); @@ -749,6 +762,7 @@ SelectionDisplay = (function() { } else if (overlay === handleTranslateZCylinder) { return handleTranslateZCone; } + return Uuid.NULL; }; // FUNCTION: MOUSE MOVE EVENT @@ -1038,13 +1052,13 @@ SelectionDisplay = (function() { var toCameraDistance = getDistanceToCamera(position); var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); - rotationX = Quat.multiply(rotation, localRotationX); + var rotationX = Quat.multiply(rotation, localRotationX); worldRotationX = rotationX; var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); - rotationY = Quat.multiply(rotation, localRotationY); + var rotationY = Quat.multiply(rotation, localRotationY); worldRotationY = rotationY; var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); - rotationZ = Quat.multiply(rotation, localRotationZ); + var rotationZ = Quat.multiply(rotation, localRotationZ); worldRotationZ = rotationZ; // in HMD we clamp the overlays to the bounding box for now so lasers can hit them @@ -1260,10 +1274,9 @@ SelectionDisplay = (function() { dimensions: stretchPanelXDimensions }); var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); - var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; - stretchPanelYDimensions.z = tempX; + stretchPanelYDimensions.z = Math.abs(stretchPanelYDimensions.x); var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 })); Overlays.editOverlay(handleStretchYPanel, { position: stretchPanelYPosition, @@ -1271,9 +1284,8 @@ SelectionDisplay = (function() { dimensions: stretchPanelYDimensions }); var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated); - var tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); - stretchPanelZDimensions.y = tempX; + stretchPanelZDimensions.y = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.z = STRETCH_PANEL_WIDTH; var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 })); Overlays.editOverlay(handleStretchZPanel, { @@ -1519,17 +1531,7 @@ SelectionDisplay = (function() { // copy of the selected entities and move the _original_ entities, not // the new ones. if (event.isAlt || doClone) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } + duplicatedEntityIDs = SelectionManager.duplicateSelection(); } else { duplicatedEntityIDs = null; } @@ -1618,8 +1620,18 @@ SelectionDisplay = (function() { grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var properties = SelectionManager.savedProperties[toMove[i]]; if (!properties) { continue; } @@ -1628,7 +1640,7 @@ SelectionDisplay = (function() { y: 0, z: vector.z }); - Entities.editEntity(SelectionManager.selections[i], { + Entities.editEntity(toMove[i], { position: newPosition }); @@ -1653,11 +1665,11 @@ SelectionDisplay = (function() { mode: mode, onBegin: function(event, pickRay, pickResult) { if (direction === TRANSLATE_DIRECTION.X) { - pickNormal = { x:0, y:0, z:1 }; + pickNormal = { x:0, y:1, z:1 }; } else if (direction === TRANSLATE_DIRECTION.Y) { - pickNormal = { x:1, y:0, z:0 }; + pickNormal = { x:1, y:0, z:1 }; } else if (direction === TRANSLATE_DIRECTION.Z) { - pickNormal = { x:0, y:1, z:0 }; + pickNormal = { x:1, y:1, z:0 }; } var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; @@ -1680,17 +1692,7 @@ SelectionDisplay = (function() { // copy of the selected entities and move the _original_ entities, not // the new ones. if (event.isAlt) { - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties - }); - } - } + duplicatedEntityIDs = SelectionManager.duplicateSelection(); } else { duplicatedEntityIDs = null; } @@ -1701,7 +1703,6 @@ SelectionDisplay = (function() { onMove: function(event) { pickRay = generalComputePickRay(event.x, event.y); - // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); var vector = Vec3.subtract(newIntersection, lastPick); @@ -1719,7 +1720,7 @@ SelectionDisplay = (function() { var dotVector = Vec3.dot(vector, projectionVector); vector = Vec3.multiply(dotVector, projectionVector); vector = grid.snapToGrid(vector); - + var wantDebug = false; if (wantDebug) { print("translateUpDown... "); @@ -1727,9 +1728,19 @@ SelectionDisplay = (function() { Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); } - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var id = SelectionManager.selections[i]; + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var id = toMove[i]; var properties = SelectionManager.savedProperties[id]; var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { position: newPosition }); @@ -2017,10 +2028,10 @@ SelectionDisplay = (function() { vector = grid.snapToSpacing(vector); var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector)); - if (directionEnum === STRETCH_DIRECTION.ALL) { - var toCameraDistance = getDistanceToCamera(position); - var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; - changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); + if (directionEnum === STRETCH_DIRECTION.ALL) { + var toCameraDistance = getDistanceToCamera(position); + var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; + changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); } var newDimensions; @@ -2166,8 +2177,19 @@ SelectionDisplay = (function() { // the selections center point. Otherwise, the rotation will be around the entities // registration point which does not need repositioning. var reposition = (SelectionManager.selections.length > 1); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toRotate = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toRotate.length; i++) { + var entityID = toRotate[i]; var initialProperties = SelectionManager.savedProperties[entityID]; var newProperties = { diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 19d4417a12..3be6ac0b00 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -2,15 +2,7 @@ var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html'); Grid = function(opts) { var that = {}; - - var colors = [ - { red: 0, green: 0, blue: 0 }, - { red: 255, green: 255, blue: 255 }, - { red: 255, green: 0, blue: 0 }, - { red: 0, green: 255, blue: 0 }, - { red: 0, green: 0, blue: 255 }, - ]; - var colorIndex = 0; + var gridColor = { red: 0, green: 0, blue: 0 }; var gridAlpha = 0.6; var origin = { x: 0, y: +MyAvatar.getJointPosition('LeftToeBase').y.toFixed(1) + 0.1, z: 0 }; var scale = 500; @@ -28,10 +20,10 @@ Grid = function(opts) { position: origin, visible: false, drawInFront: false, - color: colors[0], + color: gridColor, alpha: gridAlpha, minorGridEvery: minorGridEvery, - majorGridEvery: majorGridEvery, + majorGridEvery: majorGridEvery }); that.visible = false; @@ -39,26 +31,38 @@ Grid = function(opts) { that.getOrigin = function() { return origin; - } + }; + + that.getMinorIncrement = function() { + return minorGridEvery; + }; - that.getMinorIncrement = function() { return minorGridEvery; }; that.setMinorIncrement = function(value) { minorGridEvery = value; updateGrid(); - } - that.getMajorIncrement = function() { return majorGridEvery; }; + }; + + that.getMajorIncrement = function() { + return majorGridEvery; + }; + that.setMajorIncrement = function(value) { majorGridEvery = value; updateGrid(); }; - that.getColorIndex = function() { return colorIndex; }; - that.setColorIndex = function(value) { - colorIndex = value; + that.getColor = function() { + return gridColor; + }; + + that.setColor = function(value) { + gridColor = value; updateGrid(); }; - that.getSnapToGrid = function() { return snapToGrid; }; + that.getSnapToGrid = function() { + return snapToGrid; + }; that.setSnapToGrid = function(value) { snapToGrid = value; that.emitUpdate(); @@ -67,9 +71,11 @@ Grid = function(opts) { that.setEnabled = function(enabled) { that.enabled = enabled; updateGrid(); - } + }; - that.getVisible = function() { return that.visible; }; + that.getVisible = function() { + return that.visible; + }; that.setVisible = function(visible, noUpdate) { that.visible = visible; updateGrid(); @@ -77,7 +83,7 @@ Grid = function(opts) { if (!noUpdate) { that.emitUpdate(); } - } + }; that.snapToSurface = function(position, dimensions, registration) { if (!snapToGrid) { @@ -97,7 +103,7 @@ Grid = function(opts) { y: origin.y + (registration.y * dimensions.y), z: position.z }; - } + }; that.snapToGrid = function(position, majorOnly, dimensions, registration) { if (!snapToGrid) { @@ -121,7 +127,7 @@ Grid = function(opts) { position.z = Math.round(position.z / spacing) * spacing; return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin); - } + }; that.snapToSpacing = function(delta, majorOnly) { if (!snapToGrid) { @@ -133,11 +139,11 @@ Grid = function(opts) { var snappedDelta = { x: Math.round(delta.x / spacing) * spacing, y: Math.round(delta.y / spacing) * spacing, - z: Math.round(delta.z / spacing) * spacing, + z: Math.round(delta.z / spacing) * spacing }; return snappedDelta; - } + }; that.setPosition = function(newPosition, noUpdate) { @@ -157,7 +163,7 @@ Grid = function(opts) { majorGridEvery: majorGridEvery, gridSize: halfSize, visible: that.visible, - snapToGrid: snapToGrid, + snapToGrid: snapToGrid }); } }; @@ -183,8 +189,8 @@ Grid = function(opts) { majorGridEvery = data.majorGridEvery; } - if (data.colorIndex !== undefined) { - colorIndex = data.colorIndex; + if (data.gridColor) { + gridColor = data.gridColor; } if (data.gridSize) { @@ -196,7 +202,7 @@ Grid = function(opts) { } updateGrid(true); - } + }; function updateGrid(noUpdate) { Overlays.editOverlay(gridOverlay, { @@ -204,8 +210,8 @@ Grid = function(opts) { visible: that.visible && that.enabled, minorGridEvery: minorGridEvery, majorGridEvery: majorGridEvery, - color: colors[colorIndex], - alpha: gridAlpha, + color: gridColor, + alpha: gridAlpha }); if (!noUpdate) { @@ -219,7 +225,7 @@ Grid = function(opts) { that.addListener = function(callback) { that.onUpdate = callback; - } + }; Script.scriptEnding.connect(cleanup); updateGrid(); @@ -238,7 +244,7 @@ GridTool = function(opts) { var webView = null; webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - webView.setVisible = function(value) {}; + webView.setVisible = function(value) { }; horizontalGrid.addListener(function(data) { webView.emitScriptEvent(JSON.stringify(data)); @@ -250,8 +256,8 @@ GridTool = function(opts) { webView.webEventReceived.connect(function(data) { try { data = JSON.parse(data); - } catch(e) { - print("gridTool.js: Error parsing JSON: " + e.name + " data " + data) + } catch (e) { + print("gridTool.js: Error parsing JSON: " + e.name + " data " + data); return; } @@ -280,11 +286,11 @@ GridTool = function(opts) { that.addListener = function(callback) { listeners.push(callback); - } + }; that.setVisible = function(visible) { webView.setVisible(visible); - } + }; return that; }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 6e94fb238d..64ce73fad6 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -9,7 +9,7 @@ // /* global Tablet, Script, HMD, UserActivityLogger, Entities, Account, Wallet, ContextOverlay, Settings, Camera, Vec3, - Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow, Overlays, SoundCache, + Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow, getConnectionData, Overlays, SoundCache, DesktopPreviewProvider */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ @@ -61,8 +61,6 @@ var selectionDisplay = null; // for gridTool.js to ignore } } - Window.messageBoxClosed.connect(onMessageBoxClosed); - var onMarketplaceScreen = false; var onCommerceScreen = false; @@ -74,7 +72,7 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); } else { tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - tablet.sendToQml({ + sendToQml({ method: 'updateCheckoutQML', params: { itemId: '424611a2-73d0-4c03-9087-26a6a279257b', itemName: '2018-02-15 Finnegon', @@ -86,23 +84,13 @@ var selectionDisplay = null; // for gridTool.js to ignore } } - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; - var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; - var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg"; - var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg"; - var marketplaceButton = tablet.addButton({ - icon: NORMAL_ICON, - activeIcon: NORMAL_ACTIVE, - text: "MARKET", - sortOrder: 9 - }); - function messagesWaiting(isWaiting) { - marketplaceButton.editProperties({ - icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), - activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) - }); + if (marketplaceButton) { + marketplaceButton.editProperties({ + icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), + activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) + }); + } } function onCanWriteAssetsChanged() { @@ -110,26 +98,8 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.emitScriptEvent(message); } - function onClick() { - if (onMarketplaceScreen || onCommerceScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - Wallet.refreshWalletStatus(); - if (HMD.tabletID) { - Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); - } - showMarketplace(); - } - } - var referrerURL; // Used for updating Purchases QML - var filterText; // Used for updating Purchases QML - - var onWalletScreen = false; - var onCommerceScreen = false; var tabletShouldBeVisibleInSecondaryCamera = false; - function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { if (visibleInSecondaryCam) { // if we're potentially showing the tablet, only do so if it was visible before @@ -147,48 +117,6 @@ var selectionDisplay = null; // for gridTool.js to ignore Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); } - function onScreenChanged(type, url) { - onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; - var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH - || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); - - if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen - if (isHmdPreviewDisabledBySecurity) { - DesktopPreviewProvider.setPreviewDisabledReason("USER"); - Menu.setIsOptionChecked("Disable Preview", false); - setTabletVisibleInSecondaryCamera(true); - isHmdPreviewDisabledBySecurity = false; - } - } - - onCommerceScreen = onCommerceScreenNow; - onWalletScreen = onWalletScreenNow; - wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); - - if (url === MARKETPLACE_PURCHASES_QML_PATH) { - tablet.sendToQml({ - method: 'updatePurchases', - referrerURL: referrerURL, - filterText: filterText - }); - } - - // for toolbar mode: change button to active when window is first openend, false otherwise. - marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen }); - if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { - ContextOverlay.isInMarketplaceInspectionMode = true; - } else { - ContextOverlay.isInMarketplaceInspectionMode = false; - } - - if (!onCommerceScreen) { - tablet.sendToQml({ - method: 'inspectionCertificate_resetCert' - }); - } - } - function openWallet() { tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); } @@ -196,7 +124,7 @@ var selectionDisplay = null; // for gridTool.js to ignore function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { wireEventBridge(true); var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); - tablet.sendToQml({ + sendToQml({ method: 'inspectionCertificate_setCertificateId', entityId: currentEntityWithContextOverlay, certificateId: certificateId @@ -224,6 +152,385 @@ var selectionDisplay = null; // for gridTool.js to ignore })); } + // BEGIN AVATAR SELECTOR LOGIC + var UNSELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") + }; + var SELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") + }; + var HOVER_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") + }; + + var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; + var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; + var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + var conserveResources = true; + + var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + + function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + if (hasModel) { + var modelKey = key + "-m"; + this.model = new ExtendedOverlay(modelKey, "model", { + url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), + textures: textures(selected), + ignoreRayIntersection: true + }, false, false); + } else { + this.model = undefined; + } + this.key = key; + this.selected = selected || false; // not undefined + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... + } + // Instance methods: + ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; + }; + + ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); + }; + + function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + var delta = 0xFF - component; + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; + } + + function textures(selected, hovering) { + return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES; + } + // so we don't have to traverse the overlays to get the last one + var lastHoveringId = 0; + ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (this.model) { + this.model.editOverlay({ textures: textures(this.selected, hovering) }); + } + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + lastHoveringId = this.key; + } + }; + ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } + + this.editOverlay({ color: color(selected, this.hovering) }); + if (this.model) { + this.model.editOverlay({ textures: textures(selected) }); + } + this.selected = selected; + }; + // Class methods: + var selectedIds = []; + ExtendedOverlay.isSelected = function (id) { + return -1 !== selectedIds.indexOf(id); + }; + ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; + }; + ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { + return; + } + } + }; + ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + }; + + // hit(overlay) on the one overlay intersected by pickRay, if any. + // noHit() if no ExtendedOverlay was intersected (helps with hover) + ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); + } + return; + } + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); + return true; + } + }); + }; + + function HighlightedEntity(id, entityProperties) { + this.id = id; + this.overlay = Overlays.addOverlay('cube', { + position: entityProperties.position, + rotation: entityProperties.rotation, + dimensions: entityProperties.dimensions, + solid: false, + color: { + red: 0xF3, + green: 0x91, + blue: 0x29 + }, + ignoreRayIntersection: true, + drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene. + }); + HighlightedEntity.overlays.push(this); + } + HighlightedEntity.overlays = []; + HighlightedEntity.clearOverlays = function clearHighlightedEntities() { + HighlightedEntity.overlays.forEach(function (highlighted) { + Overlays.deleteOverlay(highlighted.overlay); + }); + HighlightedEntity.overlays = []; + }; + HighlightedEntity.updateOverlays = function updateHighlightedEntities() { + HighlightedEntity.overlays.forEach(function (highlighted) { + var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']); + Overlays.editOverlay(highlighted.overlay, { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions + }); + }); + }; + + + function addAvatarNode(id) { + var selected = ExtendedOverlay.isSelected(id); + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(selected, false), + ignoreRayIntersection: false + }, selected, !conserveResources); + } + + var pingPong = true; + function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: 0.032 * distance + }); + if (overlay.model) { + overlay.model.ping = pingPong; + overlay.model.editOverlay({ + position: target, + scale: 0.2 * distance, // constant apparent size + rotation: Camera.orientation + }); + } + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); + // We could re-populateNearbyUserList if anything added or removed, but not for now. + HighlightedEntity.updateOverlays(); + } + function removeOverlays() { + selectedIds = []; + lastHoveringId = 0; + HighlightedEntity.clearOverlays(); + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); + } + + // + // Clicks. + // + function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedIds[0] === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + sendToQml(message); + } + } + function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedIds = nextSelectedStatus ? [avatarId] : []; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: [avatarId], + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + sendToQml(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + HighlightedEntity.clearOverlays(); + if (selectedIds.length) { + Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) { + // Because lastEditedBy is per session, the vast majority of entities won't match, + // so it would probably be worth reducing marshalling costs by asking for just we need. + // However, providing property name(s) is advisory and some additional properties are + // included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation', + // and 'dimensions', too, so we might as well make use of them instead of making a second + // getEntityProperties call. + // It would be nice if we could harden this against future changes by specifying all + // and only these four in an array, but see + // https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work + var properties = Entities.getEntityProperties(id, 'lastEditedBy'); + if (ExtendedOverlay.isSelected(properties.lastEditedBy)) { + new HighlightedEntity(id, properties); + } + }); + } + return true; + }); + } + function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); + } + function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); + } + + // handy global to keep track of which hand is the mouse (if any) + var currentHandPressed = 0; + var TRIGGER_CLICK_THRESHOLD = 0.85; + var TRIGGER_PRESS_THRESHOLD = 0.05; + + function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so + ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); + } + function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. + } + + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we don't get mousePressEvents. + var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } + } + function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; + } + function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; + } + triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + // END AVATAR SELECTOR LOGIC + var grid = new Grid(); function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original @@ -415,16 +722,9 @@ var selectionDisplay = null; // for gridTool.js to ignore } } - marketplaceButton.clicked.connect(onClick); - tablet.screenChanged.connect(onScreenChanged); - Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); - ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); - GlobalServices.myUsernameChanged.connect(onUsernameChanged); - Wallet.walletStatusChanged.connect(sendCommerceSettings); - Wallet.refreshWalletStatus(); - + var referrerURL; // Used for updating Purchases QML + var filterText; // Used for updating Purchases QML function onMessage(message) { - if (message === GOTO_DIRECTORY) { tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); } else if (message === QUERY_CAN_WRITE_ASSETS) { @@ -456,7 +756,7 @@ var selectionDisplay = null; // for gridTool.js to ignore if (parsedJsonMessage.type === "CHECKOUT") { wireEventBridge(true); tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - tablet.sendToQml({ + sendToQml({ method: 'updateCheckoutQML', params: parsedJsonMessage }); @@ -470,7 +770,7 @@ var selectionDisplay = null; // for gridTool.js to ignore openLoginWindow(); } else if (parsedJsonMessage.type === "WALLET_SETUP") { wireEventBridge(true); - tablet.sendToQml({ + sendToQml({ method: 'updateWalletReferrer', referrer: "marketplace cta" }); @@ -480,63 +780,126 @@ var selectionDisplay = null; // for gridTool.js to ignore filterText = ""; tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); wireEventBridge(true); - tablet.sendToQml({ + sendToQml({ method: 'purchases_showMyItems' }); } } } - tablet.webEventReceived.connect(onMessage); - - Script.scriptEnding.connect(function () { - if (onMarketplaceScreen || onCommerceScreen) { - tablet.gotoHomeScreen(); - } - tablet.removeButton(marketplaceButton); - tablet.screenChanged.disconnect(onScreenChanged); - ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo); - tablet.webEventReceived.disconnect(onMessage); - Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - Wallet.walletStatusChanged.disconnect(sendCommerceSettings); - }); - - - - // Function Name: wireEventBridge() - // - // Description: - // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or - // disable to event bridge. - // - // Relevant Variables: - // -hasEventBridge: true/false depending on whether we've already connected the event bridge. - var hasEventBridge = false; - function wireEventBridge(on) { + function onButtonClicked() { if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); + print("Warning in buttonClicked(): 'tablet' undefined!"); return; } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } + if (onMarketplaceScreen || onCommerceScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; + if (HMD.tabletID) { + Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); } + showMarketplace(); } } + // Function Name: sendToQml() + // + // Description: + // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to + // the QML in the format "{method, params}", like json-rpc. See also fromQml(). + function sendToQml(message) { + tablet.sendToQml(message); + } + + var sendAssetRecipient; + var sendAssetParticleEffectUpdateTimer; + var particleEffectTimestamp; + var sendAssetParticleEffect; + var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250; + var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000; + var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8; + var SEND_ASSET_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 255, green: 222, blue: 255 }, + colorFinish: { red: 255, green: 229, blue: 225 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 243, green: 255, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'asset-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' + }; + + function updateSendAssetParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) { + deleteSendAssetParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendAssetParticleEffect, { + isEmitting: 0 + }); + } else if (sendAssetParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendAssetParticleEffect, { + emitAcceleration: accel, + lifespan: life + }); + } + } + + function deleteSendAssetParticleEffect() { + if (sendAssetParticleEffectUpdateTimer) { + Script.clearInterval(sendAssetParticleEffectUpdateTimer); + sendAssetParticleEffectUpdateTimer = null; + } + if (sendAssetParticleEffect) { + sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); + } + sendAssetRecipient = null; + } + + var savedDisablePreviewOptionLocked = false; + var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");; + function maybeEnableHMDPreview() { + setTabletVisibleInSecondaryCamera(true); + DesktopPreviewProvider.setPreviewDisabledReason("USER"); + Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption); + savedDisablePreviewOptionLocked = false; + } + // Function Name: fromQml() // // Description: // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML // in the format "{method, params}", like json-rpc. - var isHmdPreviewDisabledBySecurity = false; function fromQml(message) { switch (message.method) { case 'purchases_openWallet': @@ -546,7 +909,7 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'purchases_walletNotSetUp': wireEventBridge(true); - tablet.sendToQml({ + sendToQml({ method: 'updateWalletReferrer', referrer: "purchases" }); @@ -554,7 +917,7 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'checkout_walletNotSetUp': wireEventBridge(true); - tablet.sendToQml({ + sendToQml({ method: 'updateWalletReferrer', referrer: message.referrer === "itemPage" ? message.itemId : message.referrer }); @@ -600,6 +963,9 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'giftAsset': + + break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -608,21 +974,19 @@ var selectionDisplay = null; // for gridTool.js to ignore openLoginWindow(); break; case 'disableHmdPreview': - var isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); - if (!isHmdPreviewDisabled) { + if (!savedDisablePreviewOption) { + savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); + savedDisablePreviewOptionLocked = true; + } + + if (!savedDisablePreviewOption) { DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); setTabletVisibleInSecondaryCamera(false); - isHmdPreviewDisabledBySecurity = true; } break; case 'maybeEnableHmdPreview': - if (isHmdPreviewDisabledBySecurity) { - DesktopPreviewProvider.setPreviewDisabledReason("USER"); - Menu.setIsOptionChecked("Disable Preview", false); - setTabletVisibleInSecondaryCamera(true); - isHmdPreviewDisabledBySecurity = false; - } + maybeEnableHMDPreview(); break; case 'purchases_openGoTo': tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); @@ -644,24 +1008,243 @@ var selectionDisplay = null; // for gridTool.js to ignore filterText = ""; tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); wireEventBridge(true); - tablet.sendToQml({ + sendToQml({ method: 'purchases_showMyItems' }); break; case 'refreshConnections': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + print('Refreshing Connections...'); + getConnectionData(false); + } + break; case 'enable_ChooseRecipientNearbyMode': + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + } + break; case 'disable_ChooseRecipientNearbyMode': - case 'sendMoney_sendPublicly': - // NOP + // Guard to prevent this code from being executed while sending money -- + // we only want to execute this while sending non-HFC gifts + if (!onWalletScreen) { + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + } break; case 'wallet_availableUpdatesReceived': case 'purchases_availableUpdatesReceived': userHasUpdates = message.numUpdates > 0; messagesWaiting(userHasUpdates); break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties(nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); + } + } + + sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'sendAsset_sendPublicly': + if (message.assetName !== "") { + deleteSendAssetParticleEffect(); + sendAssetRecipient = message.recipient; + var amount = message.amount; + var props = SEND_ASSET_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendAssetParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendAssetParticleEffect(); + sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE); + } + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } } + // Function Name: wireEventBridge() + // + // Description: + // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or + // disable to event bridge. + // + // Relevant Variables: + // -hasEventBridge: true/false depending on whether we've already connected the event bridge. + var hasEventBridge = false; + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + // Function Name: onTabletScreenChanged() + // + // Description: + // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string + // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. + var onWalletScreen = false; + var onCommerceScreen = false; + function onTabletScreenChanged(type, url) { + onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; + var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH + || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + + if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen + maybeEnableHMDPreview(); + } + + onCommerceScreen = onCommerceScreenNow; + onWalletScreen = onWalletScreenNow; + wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + + if (url === MARKETPLACE_PURCHASES_QML_PATH) { + sendToQml({ + method: 'updatePurchases', + referrerURL: referrerURL, + filterText: filterText + }); + } + + // for toolbar mode: change button to active when window is first openend, false otherwise. + if (marketplaceButton) { + marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen }); + } + if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { + ContextOverlay.isInMarketplaceInspectionMode = true; + } else { + ContextOverlay.isInMarketplaceInspectionMode = false; + } + + if (onCommerceScreen) { + isWired = true; + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + Wallet.refreshWalletStatus(); + } else { + off(); + sendToQml({ + method: 'inspectionCertificate_resetCert' + }); + } + } + + // + // Manage the connection between the button and the window. + // + var marketplaceButton; + var buttonName = "MARKET"; + var tablet = null; + var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; + var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; + var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg"; + var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg"; + function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + marketplaceButton = tablet.addButton({ + icon: NORMAL_ICON, + activeIcon: NORMAL_ACTIVE, + text: buttonName, + sortOrder: 9 + }); + + ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); + Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + marketplaceButton.clicked.connect(onButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); + tablet.webEventReceived.connect(onMessage); + Wallet.walletStatusChanged.connect(sendCommerceSettings); + Window.messageBoxClosed.connect(onMessageBoxClosed); + + Wallet.refreshWalletStatus(); + } + var isWired = false; + var isUpdateOverlaysWired = false; + function off() { + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + + isWired = false; + } + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + } + function shutdown() { + maybeEnableHMDPreview(); + deleteSendAssetParticleEffect(); + + ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo); + Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + marketplaceButton.clicked.disconnect(onButtonClicked); + tablet.removeButton(marketplaceButton); + tablet.webEventReceived.disconnect(onMessage); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); + Window.messageBoxClosed.disconnect(onMessageBoxClosed); + + if (tablet) { + tablet.screenChanged.disconnect(onTabletScreenChanged); + if (onMarketplaceScreen) { + tablet.gotoHomeScreen(); + } + } + + off(); + } + + // + // Run the functions. + // + startup(); + Script.scriptEnding.connect(shutdown); + }()); // END LOCAL_SCOPE diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 728760c1e7..ba37f6ee4e 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars:true, plusplus:true, forin:true*/ -/*global Script, Settings, Window, Controller, Overlays, SoundArray, LODManager, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ +/*global Script, Settings, Window, Controller, Overlays, SoundArray, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ // // notifications.js // Version 0.801 @@ -84,21 +84,18 @@ var NOTIFICATION_MENU_ITEM_POST = " Notifications"; var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; - var lodTextID = false; - var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications" + var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications"; var NotificationType = { UNKNOWN: 0, SNAPSHOT: 1, - LOD_WARNING: 2, - CONNECTION_REFUSED: 3, - EDIT_ERROR: 4, - TABLET: 5, - CONNECTION: 6, - WALLET: 7, + CONNECTION_REFUSED: 2, + EDIT_ERROR: 3, + TABLET: 4, + CONNECTION: 5, + WALLET: 6, properties: [ { text: "Snapshot" }, - { text: "Level of Detail" }, { text: "Connection Refused" }, { text: "Edit error" }, { text: "Tablet" }, @@ -153,10 +150,6 @@ // This handles the final dismissal of a notification after fading function dismiss(firstNoteOut, firstButOut, firstOut) { - if (firstNoteOut === lodTextID) { - lodTextID = false; - } - Overlays.deleteOverlay(firstNoteOut); Overlays.deleteOverlay(firstButOut); notifications.splice(firstOut, 1); @@ -418,9 +411,6 @@ function deleteNotification(index) { var notificationTextID = notifications[index]; - if (notificationTextID === lodTextID) { - lodTextID = false; - } Overlays.deleteOverlay(notificationTextID); Overlays.deleteOverlay(buttons[index]); notifications.splice(index, 1); @@ -674,20 +664,6 @@ } } - LODManager.LODDecreased.connect(function () { - var warningText = "\n" + - "Due to the complexity of the content, the \n" + - "level of detail has been decreased. " + - "You can now see: \n" + - LODManager.getLODFeedbackText(); - - if (lodTextID === false) { - lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); - } else { - Overlays.editOverlay(lodTextID, { text: warningText }); - } - }); - Controller.keyPressEvent.connect(keyPressEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 647497335e..c24e34cc1b 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,6 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now -var PAL_QML_SOURCE = "hifi/Pal.qml"; var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); @@ -668,57 +667,6 @@ triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Cont triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); -// -// Manage the connection between the button and the window. -// -var button; -var buttonName = "PEOPLE"; -var tablet = null; - -function startup() { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - text: buttonName, - icon: "icons/tablet-icons/people-i.svg", - activeIcon: "icons/tablet-icons/people-a.svg", - sortOrder: 7 - }); - button.clicked.connect(onTabletButtonClicked); - tablet.screenChanged.connect(onTabletScreenChanged); - Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); - Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); - Messages.subscribe(CHANNEL); - Messages.messageReceived.connect(receiveMessage); - Users.avatarDisconnected.connect(avatarDisconnected); - AvatarList.avatarAddedEvent.connect(avatarAdded); - AvatarList.avatarRemovedEvent.connect(avatarRemoved); - AvatarList.avatarSessionChangedEvent.connect(avatarSessionChanged); -} - -startup(); - -var isWired = false; -var audioTimer; -var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) -var AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS = 300; -function off() { - if (isWired) { // It is not ok to disconnect these twice, hence guard. - Script.update.disconnect(updateOverlays); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - isWired = false; - ContextOverlay.enabled = true - } - if (audioTimer) { - Script.clearInterval(audioTimer); - } - triggerMapping.disable(); // It's ok if we disable twice. - triggerPressMapping.disable(); // see above - removeOverlays(); - Users.requestsDomainListData = false; -} function tabletVisibilityChanged() { if (!tablet.tabletShown) { @@ -728,26 +676,17 @@ function tabletVisibilityChanged() { } var onPalScreen = false; - +var PAL_QML_SOURCE = "hifi/Pal.qml"; function onTabletButtonClicked() { + if (!tablet) { + print("Warning in onTabletButtonClicked(): 'tablet' undefined!"); + return; + } if (onPalScreen) { - // for toolbar-mode: go back to home screen, this will close the window. + // In Toolbar Mode, `gotoHomeScreen` will close the app window. tablet.gotoHomeScreen(); - ContextOverlay.enabled = true; } else { - ContextOverlay.enabled = false; tablet.loadQMLSource(PAL_QML_SOURCE); - tablet.tabletShownChanged.connect(tabletVisibilityChanged); - Users.requestsDomainListData = true; - populateNearbyUserList(); - isWired = true; - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - Users.usernameFromIDReply.connect(usernameFromIDReply); - triggerMapping.enable(); - triggerPressMapping.enable(); - audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); } } var hasEventBridge = false; @@ -771,9 +710,25 @@ function onTabletScreenChanged(type, url) { // for toolbar mode: change button to active when window is first openend, false otherwise. button.editProperties({isActive: onPalScreen}); - // disable sphere overlays when not on pal screen. - if (!onPalScreen) { + if (onPalScreen) { + isWired = true; + + ContextOverlay.enabled = false; + Users.requestsDomainListData = true; + populateNearbyUserList(); + + audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); + + tablet.tabletShownChanged.connect(tabletVisibilityChanged); + Script.update.connect(updateOverlays); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + Users.usernameFromIDReply.connect(usernameFromIDReply); + triggerMapping.enable(); + triggerPressMapping.enable(); + } else { off(); + ContextOverlay.enabled = true; } } @@ -884,6 +839,58 @@ function avatarSessionChanged(avatarID) { sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] }); } + +var button; +var buttonName = "PEOPLE"; +var tablet = null; +function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/people-i.svg", + activeIcon: "icons/tablet-icons/people-a.svg", + sortOrder: 7 + }); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); + Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); + Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + Messages.subscribe(CHANNEL); + Messages.messageReceived.connect(receiveMessage); + Users.avatarDisconnected.connect(avatarDisconnected); + AvatarList.avatarAddedEvent.connect(avatarAdded); + AvatarList.avatarRemovedEvent.connect(avatarRemoved); + AvatarList.avatarSessionChangedEvent.connect(avatarSessionChanged); +} +startup(); + + +var isWired = false; +var audioTimer; +var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) +var AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS = 300; +function off() { + if (isWired) { + Script.update.disconnect(updateOverlays); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + ContextOverlay.enabled = true + triggerMapping.disable(); + triggerPressMapping.disable(); + Users.requestsDomainListData = false; + + isWired = false; + + if (audioTimer) { + Script.clearInterval(audioTimer); + } + } + + removeOverlays(); +} + function shutdown() { if (onPalScreen) { tablet.gotoHomeScreen(); @@ -901,10 +908,6 @@ function shutdown() { AvatarList.avatarSessionChangedEvent.disconnect(avatarSessionChanged); off(); } - -// -// Cleanup. -// Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index ae8ef52a15..5690c91c76 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -275,7 +275,7 @@ function onMessage(message) { } } -var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/sound-print-photo.wav")); +var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/sound-print-photo.wav"); var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx'; function printToPolaroid(image_url) { @@ -347,7 +347,7 @@ function fillImageDataFromPrevious() { story_id: previousStillSnapStoryID, blastButtonDisabled: previousStillSnapBlastingDisabled, hifiButtonDisabled: previousStillSnapHifiSharingDisabled, - errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg') + errorPath: Script.resourcesPath() + 'snapshot/img/no-image.jpg' }); } if (previousAnimatedSnapPath !== "") { @@ -356,7 +356,7 @@ function fillImageDataFromPrevious() { story_id: previousAnimatedSnapStoryID, blastButtonDisabled: previousAnimatedSnapBlastingDisabled, hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled, - errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg') + errorPath: Script.resourcesPath() + 'snapshot/img/no-image.jpg' }); } } @@ -411,8 +411,6 @@ function snapshotUploaded(isError, reply) { } else { print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID); } - } else { - print(reply); } isUploadingPrintableStill = false; } @@ -475,7 +473,7 @@ function takeSnapshot() { Menu.setIsOptionChecked("Overlays", false); } - var snapActivateSound = SoundCache.getSound(Script.resolvePath("../../resources/sounds/snap.wav")); + var snapActivateSound = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/snap.wav"); // take snapshot (with no notification) Script.setTimeout(function () { @@ -598,7 +596,7 @@ function processingGifStarted(pathStillSnapshot) { snapshotOptions = { containsGif: true, processingGif: true, - loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), + loadingGifPath: Script.resourcesPath() + 'icons/loadingDark.gif', canShare: canShare, isLoggedIn: isLoggedIn }; diff --git a/scripts/tutorials/entity_scripts/sit.js b/scripts/tutorials/entity_scripts/sit.js index 70456ea493..9c994ed2ea 100644 --- a/scripts/tutorials/entity_scripts/sit.js +++ b/scripts/tutorials/entity_scripts/sit.js @@ -12,9 +12,9 @@ Script.include("/~/system/libraries/utils.js"); if (!String.prototype.startsWith) { String.prototype.startsWith = function(searchString, position){ - position = position || 0; - return this.substr(position, searchString.length) === searchString; - }; + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; } var SETTING_KEY = "com.highfidelity.avatar.isSitting"; @@ -122,20 +122,10 @@ this.rolesToOverride = function() { return MyAvatar.getAnimationRoles().filter(function(role) { - return !(role.startsWith("right") || role.startsWith("left")); + return !(role.startsWith("right") || role.startsWith("left")); }); } - // Handler for user changing the avatar model while sitting. There's currently an issue with changing avatar models while override role animations are applied, - // so to avoid that problem, re-apply the role overrides once the model has finished changing. - this.modelURLChangeFinished = function () { - print("Sitter's model has FINISHED changing. Reapply anim role overrides."); - var roles = this.rolesToOverride(); - for (i in roles) { - MyAvatar.overrideRoleAnimation(roles[i], ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); - } - } - this.sitDown = function() { if (this.checkSeatForAvatar()) { print("Someone is already sitting in that chair."); @@ -155,11 +145,11 @@ MyAvatar.characterControllerEnabled = false; MyAvatar.hmdLeanRecenterEnabled = false; var roles = this.rolesToOverride(); - for (i in roles) { + for (var i = 0; i < roles.length; i++) { MyAvatar.overrideRoleAnimation(roles[i], ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); } - for (var i in OVERRIDEN_DRIVE_KEYS) { + for (i = 0; i < OVERRIDEN_DRIVE_KEYS.length; i++) { MyAvatar.disableDriveKey(OVERRIDEN_DRIVE_KEYS[i]); } @@ -174,14 +164,12 @@ return { headType: 0 }; }, ["headType"]); Script.update.connect(this, this.update); - MyAvatar.onLoadComplete.connect(this, this.modelURLChangeFinished); } this.standUp = function() { print("Standing up (" + this.entityID + ")"); MyAvatar.removeAnimationStateHandler(this.animStateHandlerID); Script.update.disconnect(this, this.update); - MyAvatar.onLoadComplete.disconnect(this, this.modelURLChangeFinished); if (MyAvatar.sessionUUID === this.getSeatUser()) { this.setSeatUser(null); @@ -190,12 +178,12 @@ if (Settings.getValue(SETTING_KEY) === this.entityID) { Settings.setValue(SETTING_KEY, ""); - for (var i in OVERRIDEN_DRIVE_KEYS) { + for (var i = 0; i < OVERRIDEN_DRIVE_KEYS.length; i++) { MyAvatar.enableDriveKey(OVERRIDEN_DRIVE_KEYS[i]); } var roles = this.rolesToOverride(); - for (i in roles) { + for (i = 0; i < roles.length; i++) { MyAvatar.restoreRoleAnimation(roles[i]); } MyAvatar.characterControllerEnabled = true; @@ -272,7 +260,7 @@ // Check if a drive key is pressed var hasActiveDriveKey = false; for (var i in OVERRIDEN_DRIVE_KEYS) { - if (MyAvatar.getRawDriveKey(OVERRIDEN_DRIVE_KEYS[i]) != 0.0) { + if (MyAvatar.getRawDriveKey(OVERRIDEN_DRIVE_KEYS[i]) !== 0.0) { hasActiveDriveKey = true; break; } @@ -343,7 +331,7 @@ } this.cleanupOverlay(); } - + this.clickDownOnEntity = function (id, event) { if (isInEditMode()) { return; @@ -352,4 +340,4 @@ this.sitDown(); } } -}); +}); \ No newline at end of file diff --git a/tests/baking/src/JSBakerTest.cpp b/tests/baking/src/JSBakerTest.cpp index 082ffb047f..e39ccc6b9a 100644 --- a/tests/baking/src/JSBakerTest.cpp +++ b/tests/baking/src/JSBakerTest.cpp @@ -66,12 +66,14 @@ void JSBakerTest::setTestCases() { _testCases.emplace_back("'abcd1234$%^&[](){}'\na", "'abcd1234$%^&[](){}'\na"); _testCases.emplace_back("\"abcd1234$%^&[](){}\"\na", "\"abcd1234$%^&[](){}\"\na"); _testCases.emplace_back("`abcd1234$%^&[](){}`\na", "`abcd1234$%^&[](){}`a"); + _testCases.emplace_back("\' \';", "\' \';"); + _testCases.emplace_back("\'//single line comment\nvar b=2;\';", "\'//single line comment\nvar b=2;\';"); // Edge Cases //No semicolon to terminate an expression, instead a new line used for termination _testCases.emplace_back("var x=5\nvar y=6;", "var x=5\nvar y=6;"); - + //a + ++b is minified as a+ ++b. _testCases.emplace_back("a + ++b", "a + ++b"); diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index bf79f9d3e9..43a5d2e48d 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -160,7 +160,6 @@ int main(int argc, char** argv) { QByteArray packet = file.readAll(); EntityItemPointer item = ShapeEntityItem::boxFactory(EntityItemID(), EntityItemProperties()); ReadBitstreamToTreeParams params; - params.bitstreamVersion = 33; auto start = usecTimestampNow(); for (int i = 0; i < 1000; ++i) { diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 347cfd90dc..99f9025fdd 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -24,15 +24,11 @@ extern AutoTester* autoTester; #include Test::Test() { - QString regex(EXPECTED_IMAGE_PREFIX + QString("\\\\d").repeated(NUM_DIGITS) + ".png"); - - expectedImageFilenameFormat = QRegularExpression(regex); - mismatchWindow.setModal(true); } bool Test::createTestResultsFolderPath(QString directory) { - QDateTime now = QDateTime::currentDateTime(); + QDateTime now = QDateTime::currentDateTime(); testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); QDir testResultsFolder(testResultsFolderPath); @@ -76,7 +72,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) QImage expectedImage(expectedImagesFullFilenames[i]); if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) { - messageBox.critical(0, "Internal error #1", "Images are not the same size"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); exit(-1); } @@ -84,7 +80,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) try { similarityIndex = imageComparer.compareImages(resultImage, expectedImage); } catch (...) { - messageBox.critical(0, "Internal error #2", "Image not in expected format"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Image not in expected format"); exit(-1); } @@ -131,20 +127,20 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { if (!QDir().exists(testResultsFolderPath)) { - messageBox.critical(0, "Internal error #3", "Folder " + testResultsFolderPath + " not found"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found"); exit(-1); } QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; if (!QDir().mkdir(failureFolderPath)) { - messageBox.critical(0, "Internal error #4", "Failed to create folder " + failureFolderPath); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath); exit(-1); } ++index; QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); if (!descriptionFile.open(QIODevice::ReadWrite)) { - messageBox.critical(0, "Internal error #5", "Failed to create file " + TEST_RESULTS_FILENAME); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME); exit(-1); } @@ -164,14 +160,14 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te sourceFile = testFailure._pathname + testFailure._expectedImageFilename; destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error #6", "Failed to copy " + sourceFile + " to " + destinationFile); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } sourceFile = testFailure._pathname + testFailure._actualImageFilename; destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error #7", "Failed to copy " + sourceFile + " to " + destinationFile); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } @@ -209,11 +205,6 @@ void Test::startTestsEvaluation() { QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory); QStringList expectedImagesURLs; - const QString URLPrefix("https://raw.githubusercontent.com"); - const QString githubUser("NissimHadar"); - const QString testsRepo("hifi_tests"); - const QString branch("addRecursionToAutotester"); - resultImagesFullFilenames.clear(); expectedImagesFilenames.clear(); expectedImagesFullFilenames.clear(); @@ -226,16 +217,16 @@ void Test::startTestsEvaluation() { QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); // Images are stored on GitHub as ExpectedImage_ddddd.png - // Extract the digits at the end of the filename (exluding the file extension) + // Extract the digits at the end of the filename (excluding the file extension) QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; - QString imageURLString(URLPrefix + "/" + githubUser + "/" + testsRepo + "/" + branch + "/" + - expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); + QString imageURLString("https://github.com/" + githubUser + "/hifi_tests/blob/" + gitHubBranch + "/" + + expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename + "?raw=true"); expectedImagesURLs << imageURLString; - // The image retrieved from Github needs a unique name + // The image retrieved from GitHub needs a unique name QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI."); expectedImagesFilenames << expectedImageFilename; @@ -273,25 +264,31 @@ bool Test::isAValidDirectory(QString pathname) { return true; } -void Test::importTest(QTextStream& textStream, const QString& testPathname) { - // `testPathname` includes the full path to the test. We need the portion below (and including) `tests` - QStringList filenameParts = testPathname.split('/'); +QString Test::extractPathFromTestsDown(QString fullPath) { + // `fullPath` includes the full path to the test. We need the portion below (and including) `tests` + QStringList pathParts = fullPath.split('/'); int i{ 0 }; - while (i < filenameParts.length() && filenameParts[i] != "tests") { + while (i < pathParts.length() && pathParts[i] != "tests") { ++i; } - if (i == filenameParts.length()) { - messageBox.critical(0, "Internal error #10", "Bad testPathname"); + if (i == pathParts.length()) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad testPathname"); exit(-1); } - QString filename; - for (int j = i; j < filenameParts.length(); ++j) { - filename += "/" + filenameParts[j]; + QString partialPath; + for (int j = i; j < pathParts.length(); ++j) { + partialPath += "/" + pathParts[j]; } - textStream << "Script.include(\"" << "https://raw.githubusercontent.com/" << user << "/hifi_tests/" << branch << filename + "\");" << endl; + return partialPath; +} + +void Test::importTest(QTextStream& textStream, const QString& testPathname) { + QString partialPath = extractPathFromTestsDown(testPathname); + textStream << "Script.include(\"" << "https://github.com/" << githubUser + << "/hifi_tests/blob/" << gitHubBranch << partialPath + "?raw=true\");" << endl; } // Creates a single script in a user-selected folder. @@ -307,7 +304,7 @@ void Test::createRecursiveScript() { } // This method creates a `testRecursive.js` script in every sub-folder. -void Test::createRecursiveScriptsRecursively() { +void Test::createAllRecursiveScripts() { // Select folder to start recursing from QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { @@ -353,7 +350,7 @@ void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename); if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, - "Internal Error #8", + "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\"" ); @@ -363,7 +360,9 @@ void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode QTextStream textStream(&allTestsFilename); textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl; - textStream << "var autoTester = Script.require(\"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/autoTester.js\");" << endl; + textStream << "var autoTester = Script.require(\"https://github.com/" + githubUser + "/hifi_tests/blob/" + + gitHubBranch + "/tests/utils/autoTester.js?raw=true\");" << endl; + textStream << "autoTester.enableRecursive();" << endl << endl; QVector testPathnames; @@ -454,6 +453,312 @@ void Test::createTest() { messageBox.information(0, "Success", "Test images have been created"); } +ExtractedText Test::getTestScriptLines(QString testFileName) { + ExtractedText relevantTextFromTest; + + QFile inputFile(testFileName); + inputFile.open(QIODevice::ReadOnly); + if (!inputFile.isOpen()) { + messageBox.critical(0, + "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Failed to open \"" + testFileName + ); + } + + QTextStream stream(&inputFile); + QString line = stream.readLine(); + + // Name of test is the string in the following line: + // autoTester.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {... + const QString ws("\\h*"); //white-space character + const QString functionPerformName(ws + "autoTester" + ws + "\\." + ws + "perform"); + const QString quotedString("\\\".+\\\""); + const QString ownPath("Script" + ws + "\\." + ws + "resolvePath" + ws + "\\(" + ws + "\\\"\\.\\\"" + ws + "\\)"); + const QString functionParameter("function" + ws + "\\(testType" + ws + "\\)"); + QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*"); + QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); + + // Assert platform checks that test is running on the correct OS + const QString functionAssertPlatform(ws + "autoTester" + ws + "\\." + ws + "assertPlatform"); + const QString regexAssertPlatform(ws + functionAssertPlatform + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertPlatform = QRegularExpression(regexAssertPlatform); + + // Assert display checks that test is running on the correct display + const QString functionAssertDisplay(ws + "autoTester" + ws + "\\." + ws + "assertDisplay"); + const QString regexAssertDisplay(ws + functionAssertDisplay + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertDisplay = QRegularExpression(regexAssertDisplay); + + // Assert CPU checks that test is running on the correct type of CPU + const QString functionAssertCPU(ws + "autoTester" + ws + "\\." + ws + "assertCPU"); + const QString regexAssertCPU(ws + functionAssertCPU + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertCPU = QRegularExpression(regexAssertCPU); + + // Assert GPU checks that test is running on the correct type of GPU + const QString functionAssertGPU(ws + "autoTester" + ws + "\\." + ws + "assertGPU"); + const QString regexAssertGPU(ws + functionAssertGPU + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineAssertGPU = QRegularExpression(regexAssertGPU); + + // Each step is either of the following forms: + // autoTester.addStepSnapshot("Take snapshot"... + // autoTester.addStep("Clean up after test"... + const QString functionAddStepSnapshotName(ws + "autoTester" + ws + "\\." + ws + "addStepSnapshot"); + const QString regexStepSnapshot(ws + functionAddStepSnapshotName + ws + "\\(" + ws + quotedString + ".*"); + const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot); + + const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep"); + const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ws + "\\)" + ".*"); + const QRegularExpression lineStep = QRegularExpression(regexStep); + + while (!line.isNull()) { + line = stream.readLine(); + if (lineContainingTitle.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + relevantTextFromTest.title = tokens[1]; + } else if (lineAssertPlatform.match(line).hasMatch()) { + QStringList platforms = line.split('"'); + relevantTextFromTest.platform = platforms[1]; + } else if (lineAssertDisplay.match(line).hasMatch()) { + QStringList displays = line.split('"'); + relevantTextFromTest.display = displays[1]; + } else if (lineAssertCPU.match(line).hasMatch()) { + QStringList cpus = line.split('"'); + relevantTextFromTest.cpu = cpus[1]; + } else if (lineAssertGPU.match(line).hasMatch()) { + QStringList gpus = line.split('"'); + relevantTextFromTest.gpu = gpus[1]; + } else if (lineStepSnapshot.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + QString nameOfStep = tokens[1]; + + Step *step = new Step(); + step->text = nameOfStep; + step->takeSnapshot = true; + relevantTextFromTest.stepList.emplace_back(step); + } else if (lineStep.match(line).hasMatch()) { + QStringList tokens = line.split('"'); + QString nameOfStep = tokens[1]; + + Step *step = new Step(); + step->text = nameOfStep; + step->takeSnapshot = false; + relevantTextFromTest.stepList.emplace_back(step); + } + } + + inputFile.close(); + + return relevantTextFromTest; +} + +// Create an MD file for a user-selected test. +// The folder selected must contain a script named "test.js", the file produced is named "test.md" +void Test::createMDFile() { + // Folder selection + QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly); + if (testDirectory == "") { + return; + } + + createMDFile(testDirectory); +} + +void Test::createAllMDFiles() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + // First test if top-level folder has a test.js file + const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createMDFile(topLevelDirectory); + } + + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + const QString testPathname{ directory + "/" + TEST_FILENAME }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + createMDFile(directory); + } + } + + messageBox.information(0, "Success", "MD files have been created"); +} + +void Test::createMDFile(QString testDirectory) { + // Verify folder contains test.js file + QString testFileName(testDirectory + "/" + TEST_FILENAME); + QFileInfo testFileInfo(testFileName); + if (!testFileInfo.exists()) { + messageBox.critical(0, "Error", "Could not find file: " + TEST_FILENAME); + return; + } + + ExtractedText testScriptLines = getTestScriptLines(testFileName); + + QString mdFilename(testDirectory + "/" + "test.md"); + QFile mdFile(mdFilename); + if (!mdFile.open(QIODevice::WriteOnly)) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); + exit(-1); + } + + QTextStream stream(&mdFile); + + //Test title + QString testName = testScriptLines.title; + stream << "# " << testName << "\n"; + + // Find the relevant part of the path to the test (i.e. from "tests" down + QString partialPath = extractPathFromTestsDown(testDirectory); + + stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; + + stream << "## Preconditions" << "\n"; + stream << "- In an empty region of a domain with editing rights." << "\n\n"; + + // Platform + QStringList platforms = testScriptLines.platform.split(" ");; + stream << "## Platforms\n"; + stream << "Run the test on each of the following platforms\n"; + for (int i = 0; i < platforms.size(); ++i) { + // Note that the platforms parameter may include extra spaces, these appear as empty strings in the list + if (platforms[i] != QString()) { + stream << " - " << platforms[i] << "\n"; + } + } + + // Display + QStringList displays = testScriptLines.display.split(" "); + stream << "## Displays\n"; + stream << "Run the test on each of the following displays\n"; + for (int i = 0; i < displays.size(); ++i) { + // Note that the displays parameter may include extra spaces, these appear as empty strings in the list + if (displays[i] != QString()) { + stream << " - " << displays[i] << "\n"; + } + } + + // CPU + QStringList cpus = testScriptLines.cpu.split(" "); + stream << "## Processors\n"; + stream << "Run the test on each of the following processors\n"; + for (int i = 0; i < cpus.size(); ++i) { + // Note that the cpus parameter may include extra spaces, these appear as empty strings in the list + if (cpus[i] != QString()) { + stream << " - " << cpus[i] << "\n"; + } + } + + // GPU + QStringList gpus = testScriptLines.gpu.split(" "); + stream << "## Graphics Cards\n"; + stream << "Run the test on graphics cards from each of the following vendors\n"; + for (int i = 0; i < gpus.size(); ++i) { + // Note that the gpus parameter may include extra spaces, these appear as empty strings in the list + if (gpus[i] != QString()) { + stream << " - " << gpus[i] << "\n"; + } + } + + stream << "## Steps\n"; + stream << "Press space bar to advance step by step\n\n"; + + int snapShotIndex { 0 }; + for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) { + stream << "### Step " << QString::number(i + 1) << "\n"; + stream << "- " << testScriptLines.stepList[i]->text << "\n"; + if (testScriptLines.stepList[i]->takeSnapshot) { + stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n"; + ++snapShotIndex; + } + } + + mdFile.close(); +} + +void Test::createTestsOutline() { + QString testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", ".", QFileDialog::ShowDirsOnly); + if (testsRootDirectory == "") { + return; + } + + const QString testsOutlineFilename { "testsOutline.md" }; + QString mdFilename(testsRootDirectory + "/" + testsOutlineFilename); + QFile mdFile(mdFilename); + if (!mdFile.open(QIODevice::WriteOnly)) { + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); + exit(-1); + } + + QTextStream stream(&mdFile); + + //Test title + stream << "# Outline of all tests\n"; + stream << "Directories with an appended (*) have an automatic test\n\n"; + + // We need to know our current depth, as this isn't given by QDirIterator + int rootDepth { testsRootDirectory.count('/') }; + + // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file + QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + // Ignore the utils directory + if (directory.right(5) == "utils") { + continue; + } + + // The prefix is the MarkDown prefix needed for correct indentation + // It consists of 2 spaces for each level of indentation, folled by a dash sign + int currentDepth = directory.count('/') - rootDepth; + QString prefix = QString(" ").repeated(2 * currentDepth - 1) + " - "; + + // The directory name appears after the last slash (we are assured there is at least 1). + QString directoryName = directory.right(directory.length() - directory.lastIndexOf("/") - 1); + + // autoTester is run on a clone of the repository. We use relative paths, so we can use both local disk and GitHub + // For a test in "D:/GitHub/hifi_tests/tests/content/entity/zone/ambientLightInheritance" the + // GitHub URL is "./content/entity/zone/ambientLightInheritance?raw=true" + QString partialPath = directory.right(directory.length() - (directory.lastIndexOf("/tests/") + QString("/tests").length() + 1)); + QString url = "./" + partialPath; + + stream << prefix << "[" << directoryName << "](" << url << "?raw=true" << ")"; + QFileInfo fileInfo1(directory + "/test.md"); + if (fileInfo1.exists()) { + stream << " [(test description)](" << url << "/test.md)"; + } + + QFileInfo fileInfo2(directory + "/" + TEST_FILENAME); + if (fileInfo2.exists()) { + stream << " (*)"; + } + stream << "\n"; + } + + mdFile.close(); + + messageBox.information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created"); +} + void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) { QFile::remove(destinationPNGFullFilename); @@ -526,7 +831,7 @@ QString Test::getExpectedImagePartialSourceDirectory(QString filename) { } if (i < 0) { - messageBox.critical(0, "Internal error #9", "Bad filename"); + messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad filename"); exit(-1); } diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index cd5075002a..e69459fef2 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -19,6 +19,24 @@ #include "ImageComparer.h" #include "ui/MismatchWindow.h" +class Step { +public: + QString text; + bool takeSnapshot; +}; + +using StepList = std::vector; + +class ExtractedText { +public: + QString title; + QString platform; + QString display; + QString cpu; + QString gpu; + StepList stepList; +}; + class Test { public: Test(); @@ -27,11 +45,15 @@ public: void finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar); void createRecursiveScript(); - void createRecursiveScriptsRecursively(); + void createAllRecursiveScripts(); void createRecursiveScript(QString topLevelDirectory, bool interactiveMode); void createTest(); - void deleteOldSnapshots(); + void createMDFile(); + void createAllMDFiles(); + void createMDFile(QString topLevelDirectory); + + void createTestsOutline(); bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); @@ -47,7 +69,7 @@ public: void zipAndDeleteTestResultsFolder(); bool isAValidDirectory(QString pathname); - + QString extractPathFromTestsDown(QString fullPath); QString getExpectedImageDestinationDirectory(QString filename); QString getExpectedImagePartialSourceDirectory(QString filename); @@ -62,8 +84,6 @@ private: QDir imageDirectory; - QRegularExpression expectedImageFilenameFormat; - MismatchWindow mismatchWindow; ImageComparer imageComparer; @@ -81,9 +101,11 @@ private: QStringList resultImagesFullFilenames; // Used for accessing GitHub - const QString user { "NissimHadar" }; - const QString branch { "addRecursionToAutotester" }; + const QString githubUser{ "highfidelity" }; + const QString gitHubBranch { "master" }; const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" }; + + ExtractedText getTestScriptLines(QString testFileName); }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index a5e13331dd..21acfe9569 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -28,12 +28,24 @@ void AutoTester::on_createRecursiveScriptButton_clicked() { test->createRecursiveScript(); } -void AutoTester::on_createRecursiveScriptsRecursivelyButton_clicked() { - test->createRecursiveScriptsRecursively(); +void AutoTester::on_createAllRecursiveScriptsButton_clicked() { + test->createAllRecursiveScripts(); } void AutoTester::on_createTestButton_clicked() { - test->createTest(); + test->createTest(); +} + +void AutoTester::on_createMDFileButton_clicked() { + test->createMDFile(); +} + +void AutoTester::on_createAllMDFilesButton_clicked() { + test->createAllMDFiles(); +} + +void AutoTester::on_createTestsOutlineButton_clicked() { + test->createTestsOutline(); } void AutoTester::on_closeButton_clicked() { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 938e7ca2d2..1788e97177 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -28,8 +28,11 @@ public: private slots: void on_evaluateTestsButton_clicked(); void on_createRecursiveScriptButton_clicked(); - void on_createRecursiveScriptsRecursivelyButton_clicked(); - void on_createTestButton_clicked(); + void on_createAllRecursiveScriptsButton_clicked(); + void on_createTestButton_clicked(); + void on_createMDFileButton_clicked(); + void on_createAllMDFilesButton_clicked(); + void on_createTestsOutlineButton_clicked(); void on_closeButton_clicked(); void saveImage(int index); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 55c3897e58..2eb1314481 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -7,7 +7,7 @@ 0 0 607 - 395 + 514 @@ -17,8 +17,8 @@ - 20 - 300 + 360 + 400 220 40 @@ -44,7 +44,7 @@ 20 - 135 + 285 220 40 @@ -57,7 +57,7 @@ 360 - 75 + 35 220 40 @@ -70,7 +70,7 @@ 23 - 100 + 250 131 20 @@ -86,7 +86,7 @@ 20 - 190 + 340 255 23 @@ -99,13 +99,52 @@ 360 - 140 + 100 220 40 - Create Recursive Scripts Recursively + Create all Recursive Scripts + + + + + + 20 + 80 + 220 + 40 + + + + Create MD file + + + + + + 20 + 130 + 220 + 40 + + + + Create all MD files + + + + + + 20 + 180 + 220 + 40 + + + + Create Tests Outline diff --git a/tools/bake-tools/bake.py b/tools/bake-tools/bake.py new file mode 100644 index 0000000000..edbfdf9e9f --- /dev/null +++ b/tools/bake-tools/bake.py @@ -0,0 +1,112 @@ +import os, json, sys, shutil, subprocess, shlex, time +EXE = os.environ['HIFI_OVEN'] + +def getRelativePath(path1, path2, stop): + parts1 = path1.split('/'); + parts2 = path2.split('/'); + if len(parts1) <= len(parts2): + for part in parts1: + if part != stop and part != '': + index = parts2.index(part) + parts2.pop(index) + return os.path.join(*parts2) + +def listFiles(directory, extension): + items = os.listdir(directory) + fileList = [] + for f in items: + if f.endswith('.' + extension): + fileList.append(f) + return fileList + +def camelCaseString(string): + string = string.replace('-', ' ') + return ''.join(x for x in string.title() if not x.isspace()) + +def groupFiles(originalDirectory, newDirectory, files): + for file in files: + newPath = os.sep.join([newDirectory, file]) + originalPath = os.sep.join([originalDirectory, file]) + shutil.move(originalPath, newPath) + +def groupKTXFiles(directory, filePath): + baseFile = os.path.basename(filePath) + filename = os.path.splitext(baseFile)[0] + camelCaseFileName = camelCaseString(filename) + path = os.sep.join([directory, camelCaseFileName]) + files = listFiles(directory, 'ktx') + if len(files) > 0: + createDirectory(path) + groupFiles(directory, path, files) + + newFilePath = os.sep.join([path, baseFile+'.baked.fbx']) + originalFilePath = os.sep.join([directory, baseFile+'.baked.fbx']) + originalFilePath.strip() + shutil.move(originalFilePath, newFilePath) + +def bakeFile(filePath, outputDirectory, fileType): + createDirectory(outputDirectory) + cmd = EXE + ' -i ' + filePath + ' -o ' + outputDirectory + ' -t ' + fileType + args = shlex.split(cmd) + process = subprocess.Popen(cmd, stdout=False, stderr=False) + process.wait() + bakedFile = os.path.splitext(filePath)[0] + if fileType == 'fbx': + groupKTXFiles(outputDirectory, bakedFile) + +def bakeFilesInDirectory(directory, outputDirectory): + rootDirectory = os.path.basename(os.path.normpath(directory)) + for root, subFolders, filenames in os.walk(directory): + for filename in filenames: + appendPath = getRelativePath(directory, root, rootDirectory); + name, ext = os.path.splitext('file.txt') + if filename.endswith('.fbx'): + filePath = os.sep.join([root, filename]) + absFilePath = os.path.abspath(filePath) + outputFolder = os.path.join(outputDirectory, appendPath) + print "Baking file: " + filename + bakeFile(absFilePath, outputFolder, 'fbx') + elif os.path.basename(root) == 'skyboxes': + filePath = os.sep.join([root, filename]) + absFilePath = os.path.abspath(filePath) + outputFolder = os.path.join(outputDirectory, appendPath) + print "Baking file: " + filename + bakeType = os.path.splitext(filename)[1][1:] + bakeFile(absFilePath, outputFolder, bakeType) + else: + filePath = os.sep.join([root, filename]) + absFilePath = os.path.abspath(filePath) + outputFolder = os.path.join(outputDirectory, appendPath) + newFilePath = os.sep.join([outputFolder, filename]) + createDirectory(outputFolder) + print "moving file: " + filename + " to: " + outputFolder + shutil.copy(absFilePath, newFilePath) + +def createDirectory(directory): + if not os.path.exists(directory): + os.makedirs(directory) + +def checkIfExeExists(): + if not os.path.isfile(EXE) and os.access(EXE, os.X_OK): + print 'HIFI_OVEN evironment variable is not set' + sys.exit() + +def handleOptions(): + option = sys.argv[1] + if option == '--help' or option == '-h': + print 'Usage: bake.py INPUT_DIRECTORY[directory to bake] OUTPUT_DIRECTORY[directory to place backed files]' + print 'Note: Output directory will be created if directory does not exist' + sys.exit() + +def main(): + argsLength = len(sys.argv) + if argsLength == 3: + checkIfExeExists() + rootDirectory = sys.argv[1] + outputDirectory = os.path.abspath(sys.argv[2]) + createDirectory(outputDirectory) + bakeFilesInDirectory(rootDirectory, outputDirectory) + elif argsLength == 2: + handleOptions() + +main() diff --git a/tools/bake-tools/convertToRelativePaths.py b/tools/bake-tools/convertToRelativePaths.py new file mode 100644 index 0000000000..70a6a30cfb --- /dev/null +++ b/tools/bake-tools/convertToRelativePaths.py @@ -0,0 +1,95 @@ +import json, os, sys, gzip + +prefix = 'file:///~/' +MAP = {} +def createAssetMapping(assetDirectory): + baseDirectory = os.path.basename(os.path.normpath(assetDirectory)) + for root, subfolder, filenames in os.walk(assetDirectory): + for filename in filenames: + if not filename.endswith('.ktx') or os.path.basename(root) == 'skyboxes': + substring = os.path.commonprefix([assetDirectory, root]) + newPath = root.replace(substring, ''); + filePath = os.sep.join([newPath, filename]) + if filePath[0] == '\\': + filePath = filePath[1:] + finalPath = prefix + baseDirectory + '/' + filePath + finalPath = finalPath.replace('\\', '/') + file = os.path.splitext(filename)[0] + file = os.path.splitext(file)[0] + MAP[file] = finalPath + +def hasURL(prop): + if "URL" in prop: + return True + return False + + +def handleURL(url): + newUrl = url + if "atp:" in url: + baseFilename = os.path.basename(url) + filename = os.path.splitext(baseFilename)[0] + newUrl = MAP[filename] + print url + ' -> ' + newUrl + return newUrl + +def handleOptions(): + option = sys.argv[1] + if option == '--help' or option == '-h': + print 'Usage: convertToRelativePaths.py INPUT[json file you want to update the urls] INPUT[directory that the baked files are located in]' + sys.exit() + +def main(): + argsLength = len(sys.argv) + if argsLength == 3: + jsonFile = sys.argv[1] + assetDirectory = sys.argv[2] + inputFilename = os.path.basename(jsonFile); + absPath = os.path.abspath(assetDirectory) + finalPath = absPath.replace('\\', '/'); + gzipFile = finalPath + '/' + inputFilename + '.gz' + print gzipFile + createAssetMapping(assetDirectory) + f = open(jsonFile) + data = json.load(f) + f.close() + for entity in data['Entities']: + for prop in entity: + value = entity[prop] + if hasURL(prop): + value = handleURL(value) + if prop == "script": + value = handleURL(value) + if prop == "textures": + try: + tmp = json.loads(value) + for index in tmp: + tmp[index] = handleURL(tmp[index]) + value = json.dumps(tmp) + except: + value = handleURL(value) + + + if prop == "ambientLight": + for index in value: + value[index] = handleURL(value[index]) + + if prop == "skybox": + for index in value: + value[index] = handleURL(value[index]) + + if prop == "serverScripts": + value = handleURL(value) + + entity[prop] = value + + + jsonString = json.dumps(data) + jsonBytes= jsonString.encode('utf-8') + with gzip.GzipFile(gzipFile, 'w') as fout: # 4. gzip + fout.write(jsonBytes) + + elif argsLength == 2: + handleOptions() + +main() diff --git a/tools/hfudt.lua b/tools/hfudt.lua new file mode 100644 index 0000000000..f45315a3de --- /dev/null +++ b/tools/hfudt.lua @@ -0,0 +1,571 @@ +print("Loading hfudt") + +-- create the HFUDT protocol +p_hfudt = Proto("hfudt", "HFUDT Protocol") + +-- create fields shared between packets in HFUDT +local f_data = ProtoField.string("hfudt.data", "Data") + +-- create the fields for data packets in HFUDT +local f_length = ProtoField.uint16("hfudt.length", "Length", base.DEC) +local f_control_bit = ProtoField.uint8("hfudt.control", "Control Bit", base.DEC) +local f_reliable_bit = ProtoField.uint8("hfudt.reliable", "Reliability Bit", base.DEC) +local f_message_bit = ProtoField.uint8("hfudt.message", "Message Bit", base.DEC) +local f_obfuscation_level = ProtoField.uint8("hfudt.obfuscation_level", "Obfuscation Level", base.DEC) +local f_sequence_number = ProtoField.uint32("hfudt.sequence_number", "Sequence Number", base.DEC) +local f_message_position = ProtoField.uint8("hfudt.message_position", "Message Position", base.DEC) +local f_message_number = ProtoField.uint32("hfudt.message_number", "Message Number", base.DEC) +local f_message_part_number = ProtoField.uint32("hfudt.message_part_number", "Message Part Number", base.DEC) +local f_type = ProtoField.uint8("hfudt.type", "Type", base.DEC) +local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC) +local f_type_text = ProtoField.string("hfudt.type_text", "TypeText") +local f_sender_id = ProtoField.guid("hfudt.sender_id", "Sender ID", base.DEC) + +-- create the fields for control packets in HFUDT +local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC) +local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII) +local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC) +local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC) +local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC) +local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) + +-- avatar data fields +local f_avatar_data_id = ProtoField.guid("hfudt.avatar_id", "Avatar ID", base.DEC) +local f_avatar_data_has_flags = ProtoField.string("hfudt.avatar_has_flags", "Has Flags") +local f_avatar_data_position = ProtoField.string("hfudt.avatar_data_position", "Position") +local f_avatar_data_dimensions = ProtoField.string("hfudt.avatar_data_dimensions", "Dimensions") +local f_avatar_data_offset = ProtoField.string("hfudt.avatar_data_offset", "Offset") +local f_avatar_data_look_at_position = ProtoField.string("hfudt.avatar_data_look_at_position", "Look At Position") +local f_avatar_data_audio_loudness = ProtoField.string("hfudt.avatar_data_audio_loudness", "Audio Loudness") +local f_avatar_data_additional_flags = ProtoField.string("hfudt.avatar_data_additional_flags", "Additional Flags") +local f_avatar_data_parent_id = ProtoField.guid("hfudt.avatar_data_parent_id", "Parent ID") +local f_avatar_data_parent_joint_index = ProtoField.string("hfudt.avatar_data_parent_joint_index", "Parent Joint Index") +local f_avatar_data_local_position = ProtoField.string("hfudt.avatar_data_local_position", "Local Position") +local f_avatar_data_valid_rotations = ProtoField.string("hfudt.avatar_data_valid_rotations", "Valid Rotations") +local f_avatar_data_valid_translations = ProtoField.string("hfudt.avatar_data_valid_translations", "Valid Translations") +local f_avatar_data_default_rotations = ProtoField.string("hfudt.avatar_data_default_rotations", "Valid Default") +local f_avatar_data_default_translations = ProtoField.string("hfudt.avatar_data_default_translations", "Valid Default") + +local SEQUENCE_NUMBER_MASK = 0x07FFFFFF + +p_hfudt.fields = { + f_length, + f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, + f_sender_id, f_avatar_data_id, f_avatar_data_parent_id, + f_message_position, f_message_number, f_message_part_number, f_obfuscation_level, + f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end, + f_data +} + +p_hfudt.prefs["udp_port"] = Pref.uint("UDP Port", 40102, "UDP Port for HFUDT Packets (udt::Socket bound port)") + +local control_types = { + [0] = { "ACK", "Acknowledgement" }, + [1] = { "ACK2", "Acknowledgement of acknowledgement" }, + [2] = { "LightACK", "Light Acknowledgement" }, + [3] = { "NAK", "Loss report (NAK)" }, + [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, + [5] = { "Handshake", "Handshake" }, + [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, + [7] = { "ProbeTail", "Probe tail" }, + [8] = { "HandshakeRequest", "Request a Handshake" } +} + +local message_positions = { + [0] = "ONLY", + [1] = "LAST", + [2] = "FIRST", + [3] = "MIDDLE" +} + +local packet_types = { + [0] = "Unknown", + [1] = "StunResponse", + [2] = "DomainList", + [3] = "Ping", + [4] = "PingReply", + [5] = "KillAvatar", + [6] = "AvatarData", + [7] = "InjectAudio", + [8] = "MixedAudio", + [9] = "MicrophoneAudioNoEcho", + [10] = "MicrophoneAudioWithEcho", + [11] = "BulkAvatarData", + [12] = "SilentAudioFrame", + [13] = "DomainListRequest", + [14] = "RequestAssignment", + [15] = "CreateAssignment", + [16] = "DomainConnectionDenied", + [17] = "MuteEnvironment", + [18] = "AudioStreamStats", + [19] = "DomainServerPathQuery", + [20] = "DomainServerPathResponse", + [21] = "DomainServerAddedNode", + [22] = "ICEServerPeerInformation", + [23] = "ICEServerQuery", + [24] = "OctreeStats", + [25] = "Jurisdiction", + [26] = "JurisdictionRequest", + [27] = "AssignmentClientStatus", + [28] = "NoisyMute", + [29] = "AvatarIdentity", + [30] = "AvatarBillboard", + [31] = "DomainConnectRequest", + [32] = "DomainServerRequireDTLS", + [33] = "NodeJsonStats", + [34] = "OctreeDataNack", + [35] = "StopNode", + [36] = "AudioEnvironment", + [37] = "EntityEditNack", + [38] = "ICEServerHeartbeat", + [39] = "ICEPing", + [40] = "ICEPingReply", + [41] = "EntityData", + [42] = "EntityQuery", + [43] = "EntityAdd", + [44] = "EntityErase", + [45] = "EntityEdit", + [46] = "DomainServerConnectionToken", + [47] = "DomainSettingsRequest", + [48] = "DomainSettings", + [49] = "AssetGet", + [50] = "AssetGetReply", + [51] = "AssetUpload", + [52] = "AssetUploadReply", + [53] = "AssetGetInfo", + [54] = "AssetGetInfoReply" +} + +function p_hfudt.dissector (buf, pinfo, root) + + -- make sure this isn't a STUN packet - those don't follow HFUDT format + if pinfo.dst == Address.ip("stun.highfidelity.io") then return end + + -- validate that the packet length is at least the minimum control packet size + if buf:len() < 4 then return end + + -- create a subtree for HFUDT + subtree = root:add(p_hfudt, buf(0)) + + -- set the packet length + subtree:add(f_length, buf:len()) + + -- pull out the entire first word + local first_word = buf(0, 4):le_uint() + + -- pull out the control bit and add it to the subtree + local control_bit = bit32.rshift(first_word, 31) + subtree:add(f_control_bit, control_bit) + + local data_length = 0 + + if control_bit == 1 then + -- dissect the control packet + pinfo.cols.protocol = p_hfudt.name .. " Control" + + -- remove the control bit and shift to the right to get the type value + local shifted_type = bit32.rshift(bit32.lshift(first_word, 1), 17) + local type = subtree:add(f_control_type, shifted_type) + + if control_types[shifted_type] ~= nil then + -- if we know this type then add the name + type:append_text(" (".. control_types[shifted_type][1] .. ")") + + subtree:add(f_control_type_text, control_types[shifted_type][1]) + end + + if shifted_type == 0 or shifted_type == 1 then + + -- this has a sub-sequence number + local second_word = buf(4, 4):le_uint() + subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) + + local data_index = 8 + + if shifted_type == 0 then + -- if this is an ACK let's read out the sequence number + local sequence_number = buf(8, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_index = data_index + 4 + end + + data_length = buf:len() - data_index + + -- set the data from whatever is left in the packet + subtree:add(f_data, buf(data_index, data_length)) + + elseif shifted_type == 2 then + -- this is a Light ACK let's read out the sequence number + local sequence_number = buf(4, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_length = buf:len() - 4 + + -- set the data from whatever is left in the packet + subtree:add(f_data, buf(4, data_length)) + elseif shifted_type == 3 or shifted_type == 4 then + if buf:len() <= 12 then + -- this is a NAK pull the sequence number or range + local sequence_number = buf(4, 4):le_uint() + subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_length = buf:len() - 4 + + if buf:len() > 8 then + local range_end = buf(8, 4):le_uint() + subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) + + data_length = data_length - 4 + end + end + else + data_length = buf:len() - 4 + + -- no sub-sequence number, just read the data + subtree:add(f_data, buf(4, data_length)) + end + else + -- dissect the data packet + pinfo.cols.protocol = p_hfudt.name + + -- set the reliability bit + subtree:add(f_reliable_bit, bit32.rshift(first_word, 30)) + + local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29)) + + -- set the message bit + subtree:add(f_message_bit, message_bit) + + -- read the obfuscation level + local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) + subtree:add(f_obfuscation_level, obfuscation_bits) + + -- read the sequence number + subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) + + local payload_offset = 4 + + -- if the message bit is set, handle the second word + if message_bit == 1 then + payload_offset = 12 + + local second_word = buf(4, 4):le_uint() + + -- read message position from upper 2 bits + local message_position = bit32.rshift(second_word, 30) + local position = subtree:add(f_message_position, message_position) + + if message_positions[message_position] ~= nil then + -- if we know this position then add the name + position:append_text(" (".. message_positions[message_position] .. ")") + end + + -- read message number from lower 30 bits + subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + + -- read the message part number + subtree:add(f_message_part_number, buf(8, 4):le_uint()) + end + + -- read the type + local packet_type = buf(payload_offset, 1):le_uint() + local ptype = subtree:add(f_type, packet_type) + if packet_types[packet_type] ~= nil then + subtree:add(f_type_text, packet_types[packet_type]) + -- if we know this packet type then add the name + ptype:append_text(" (".. packet_types[packet_type] .. ")") + end + + -- read the version + subtree:add(f_version, buf(payload_offset + 1, 1):le_uint()) + + -- read node GUID + local sender_id = buf(payload_offset + 2, 16) + subtree:add(f_sender_id, sender_id) + + local i = payload_offset + 18 + + -- skip MD6 checksum + 16 bytes + i = i + 16 + + -- AvatarData or BulkAvatarDataPacket + if packet_type == 6 or packet_type == 11 then + + local avatar_data + if packet_type == 6 then + -- AvatarData packet + + -- avatar_id is same as sender_id + subtree:add(f_avatar_data_id, sender_id) + + -- uint16 sequence_number + local sequence_number = buf(i, 2):le_uint() + i = i + 2 + + local avatar_data_packet_len = buf:len() - i + avatar_data = decode_avatar_data_packet(buf(i, avatar_data_packet_len)) + i = i + avatar_data_packet_len + + add_avatar_data_subtrees(avatar_data) + + else + -- BulkAvatarData packet + while i < buf:len() do + -- avatar_id is first 16 bytes + subtree:add(f_avatar_data_id, buf(i, 16)) + i = i + 16 + + local avatar_data_packet_len = buf:len() - i + avatar_data = decode_avatar_data_packet(buf(i, avatar_data_packet_len)) + i = i + avatar_data_packet_len + + add_avatar_data_subtrees(avatar_data) + end + end + end + end + + -- return the size of the header + return buf:len() + +end + +function add_avatar_data_subtrees(avatar_data) + if avatar_data["has_flags"] then + subtree:add(f_avatar_data_has_flags, avatar_data["has_flags"]) + end + if avatar_data["position"] then + subtree:add(f_avatar_data_position, avatar_data["position"]) + end + if avatar_data["dimensions"] then + subtree:add(f_avatar_data_dimensions, avatar_data["dimensions"]) + end + if avatar_data["offset"] then + subtree:add(f_avatar_data_offset, avatar_data["offset"]) + end + if avatar_data["look_at_position"] then + subtree:add(f_avatar_data_look_at_position, avatar_data["look_at_position"]) + end + if avatar_data["audio_loudness"] then + subtree:add(f_avatar_data_audio_loudness, avatar_data["audio_loudness"]) + end + if avatar_data["additional_flags"] then + subtree:add(f_avatar_data_additional_flags, avatar_data["additional_flags"]) + end + if avatar_data["parent_id"] then + subtree:add(f_avatar_data_parent_id, avatar_data["parent_id"]) + end + if avatar_data["parent_joint_index"] then + subtree:add(f_avatar_data_parent_joint_index, avatar_data["parent_joint_index"]) + end + if avatar_data["local_position"] then + subtree:add(f_avatar_data_local_position, avatar_data["local_position"]) + end + if avatar_data["valid_rotations"] then + subtree:add(f_avatar_data_valid_rotations, avatar_data["valid_rotations"]) + end + if avatar_data["valid_translations"] then + subtree:add(f_avatar_data_valid_translations, avatar_data["valid_translations"]) + end + if avatar_data["default_rotations"] then + subtree:add(f_avatar_data_default_rotations, avatar_data["default_rotations"]) + end + if avatar_data["default_translations"] then + subtree:add(f_avatar_data_default_translations, avatar_data["default_translations"]) + end +end + +function decode_vec3(buf) + local i = 0 + local x = buf(i, 4):le_float() + i = i + 4 + local y = buf(i, 4):le_float() + i = i + 4 + local z = buf(i, 4):le_float() + i = i + 4 + return {x, y, z} +end + +function decode_validity_bits(buf, num_bits) + -- first pass, decode each bit into an array of booleans + local i = 0 + local bit = 0 + local booleans = {} + for n = 1, num_bits do + local value = (bit32.band(buf(i, 1):uint(), bit32.lshift(1, bit)) ~= 0) + booleans[#booleans + 1] = value + bit = bit + 1 + if bit == 8 then + i = i + 1 + bit = 0 + end + end + + -- second pass, create a list of indices whos booleans are true + local result = {} + for n = 1, #booleans do + if booleans[n] then + result[#result + 1] = n + end + end + + return result +end + +function decode_avatar_data_packet(buf) + + local i = 0 + local result = {} + + -- uint16 has_flags + local has_flags = buf(i, 2):le_uint() + i = i + 2 + + local has_global_position = (bit32.band(has_flags, 1) ~= 0) + local has_bounding_box = (bit32.band(has_flags, 2) ~= 0) + local has_orientation = (bit32.band(has_flags, 4) ~= 0) + local has_scale = (bit32.band(has_flags, 8) ~= 0) + local has_look_at_position = (bit32.band(has_flags, 16) ~= 0) + local has_audio_loudness = (bit32.band(has_flags, 32) ~= 0) + local has_sensor_to_world_matrix = (bit32.band(has_flags, 64) ~= 0) + local has_additional_flags = (bit32.band(has_flags, 128) ~= 0) + local has_parent_info = (bit32.band(has_flags, 256) ~= 0) + local has_local_position = (bit32.band(has_flags, 512) ~= 0) + local has_face_tracker_info = (bit32.band(has_flags, 1024) ~= 0) + local has_joint_data = (bit32.band(has_flags, 2048) ~= 0) + local has_joint_default_pose_flags = (bit32.band(has_flags, 4096) ~= 0) + + result["has_flags"] = string.format("HasFlags: 0x%x", has_flags) + + if has_global_position then + local position = decode_vec3(buf(i, 12)) + result["position"] = string.format("Position: %.3f, %.3f, %.3f", position[1], position[2], position[3]) + i = i + 12 + end + + if has_bounding_box then + local dimensions = decode_vec3(buf(i, 12)) + i = i + 12 + local offset = decode_vec3(buf(i, 12)) + i = i + 12 + result["dimensions"] = string.format("Dimensions: %.3f, %.3f, %.3f", dimensions[1], dimensions[2], dimensions[3]) + result["offset"] = string.format("Offset: %.3f, %.3f, %.3f", offset[1], offset[2], offset[3]) + end + + if has_orientation then + -- TODO: orientation is hard to decode... + i = i + 6 + end + + if has_scale then + -- TODO: scale is hard to decode... + i = i + 2 + end + + if has_look_at_position then + local look_at = decode_vec3(buf(i, 12)) + i = i + 12 + result["look_at_position"] = string.format("Look At Position: %.3f, %.3f, %.3f", look_at[1], look_at[2], look_at[3]) + end + + if has_audio_loudness then + local loudness = buf(i, 1):uint() + i = i + 1 + result["audio_loudness"] = string.format("Audio Loudness: %d", loudness) + end + + if has_sensor_to_world_matrix then + -- TODO: sensor to world matrix is hard to decode + i = i + 20 + end + + if has_additional_flags then + local flags = buf(i, 1):uint() + i = i + 1 + result["additional_flags"] = string.format("Additional Flags: 0x%x", flags) + end + + if has_parent_info then + local parent_id = buf(i, 16) + i = i + 16 + local parent_joint_index = buf(i, 2):le_int() + i = i + 2 + result["parent_id"] = parent_id + result["parent_joint_index"] = string.format("Parent Joint Index: %d", parent_joint_index) + end + + if has_local_position then + local local_pos = decode_vec3(buf(i, 12)) + i = i + 12 + result["local_position"] = string.format("Local Position: %.3f, %.3f, %.3f", local_pos[1], local_pos[2], local_pos[3]) + end + + if has_face_tracker_info then + local left_eye_blink = buf(i, 4):le_float() + i = i + 4 + local right_eye_blink = buf(i, 4):le_float() + i = i + 4 + local average_loudness = buf(i, 4):le_float() + i = i + 4 + local brow_audio_lift = buf(i, 4):le_float() + i = i + 4 + local num_blendshape_coefficients = buf(i, 1):uint() + i = i + 1 + local blendshape_coefficients = {} + for n = 1, num_blendshape_coefficients do + blendshape_coefficients[n] = buf(i, 4):le_float() + i = i + 4 + end + -- TODO: insert blendshapes into result + end + + if has_joint_data then + + local num_joints = buf(i, 1):uint() + i = i + 1 + local num_validity_bytes = math.ceil(num_joints / 8) + + local indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["valid_rotations"] = "Valid Rotations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + + -- TODO: skip rotations for now + i = i + #indices * 6 + + indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["valid_translations"] = "Valid Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + + -- TODO: skip translations for now + i = i + #indices * 6 + + -- TODO: skip hand controller data + i = i + 24 + + end + + if has_joint_default_pose_flags then + local num_joints = buf(i, 1):uint() + i = i + 1 + local num_validity_bytes = math.ceil(num_joints / 8) + + local indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["default_rotations"] = "Default Rotations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + + indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["default_translations"] = "Default Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + end + + return result +end + +function p_hfudt.init() + local udp_dissector_table = DissectorTable.get("udp.port") + + for port=1000, 65000 do + udp_dissector_table:add(port, p_hfudt) + end +end diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 292523a813..4a6c18f243 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -13,5 +13,5 @@ file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} COMMAND ${NPM_EXECUTABLE} --no-progress install && ${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR} WORKING_DIRECTORY ${JSDOC_WORKING_DIR} - COMMENT "generate the JSDoc JSON for the JSConsole auto-completer" + COMMENT "generate the JSDoc JSON" ) diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 1f73f14b2b..e4da94ccd5 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -24,11 +24,12 @@ exports.handlers = { '../../libraries/animation/src', '../../libraries/avatars/src', '../../libraries/controllers/src/controllers/', - '../../libraries/graphics-scripting/src/graphics-scripting/', + '../../libraries/display-plugins/src/display-plugins/', '../../libraries/entities/src', + '../../libraries/graphics-scripting/src/graphics-scripting/', '../../libraries/model-networking/src/model-networking/', - '../../libraries/octree/src', '../../libraries/networking/src', + '../../libraries/octree/src', '../../libraries/physics/src', '../../libraries/pointers/src', '../../libraries/script-engine/src', diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index f5af5455fb..35550cdca8 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -18,6 +18,7 @@ #include "ModelBakingLoggingCategory.h" #include "BakerCLI.h" #include "FBXBaker.h" +#include "JSBaker.h" #include "TextureBaker.h" BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { @@ -34,6 +35,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& qDebug() << "Baking file type: " << type; static const QString MODEL_EXTENSION { "fbx" }; + static const QString SCRIPT_EXTENSION { "js" }; QString extension = type; @@ -44,6 +46,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& // check what kind of baker we should be creating bool isFBX = extension == MODEL_EXTENSION; + bool isScript = extension == SCRIPT_EXTENSION; bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1()); @@ -57,12 +60,16 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); + } else if (isScript) { + _baker = std::unique_ptr { new JSBaker(inputUrl, outputPath) }; + _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else if (isSupportedImage) { _baker = std::unique_ptr { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; QCoreApplication::exit(OVEN_STATUS_CODE_FAIL); + return; } // invoke the bake method on the baker thread diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index 4f5b6607b0..bf33b625dd 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -14,6 +14,7 @@ #include #include +#include #include @@ -31,6 +32,8 @@ class BakerCLI : public QObject { public: BakerCLI(OvenCLIApplication* parent); + +public slots: void bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type = QString::null); private slots: diff --git a/tools/oven/src/OvenCLIApplication.cpp b/tools/oven/src/OvenCLIApplication.cpp index 38d9963eeb..2fb8ea03f2 100644 --- a/tools/oven/src/OvenCLIApplication.cpp +++ b/tools/oven/src/OvenCLIApplication.cpp @@ -40,7 +40,8 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; - cli->bakeFile(inputUrl, outputUrl.toString(), type); + QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl), + Q_ARG(QString, outputUrl.toString()), Q_ARG(QString, type)); } else { parser.showHelp(); QCoreApplication::quit();