diff --git a/.gitignore b/.gitignore index 3f58e46b69..5a965b494c 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,6 @@ tools/unity-avatar-exporter/Logs tools/unity-avatar-exporter/Packages tools/unity-avatar-exporter/ProjectSettings tools/unity-avatar-exporter/Temp - +server-console/package-lock.json +vcpkg/ +/tools/nitpick/compiledResources diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a2e57dd5..6956fd22c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,8 +108,10 @@ set(PLATFORM_QT_GL OpenGL) if (USE_GLES) add_definitions(-DUSE_GLES) + add_definitions(-DGPU_POINTER_STORAGE_SHARED) set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles) else() + add_definitions(-DGPU_POINTER_STORAGE_RAW) set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl) endif() diff --git a/INSTALL.md b/INSTALL.md index 00be5f2f8f..bcbf93eef3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,7 +4,8 @@ During generation, CMake should produce an `install` target and a `package` targ ### Install -The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`. +The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`. +This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/Program Files/hifi` and stored in `build/CMakeCache.txt` ### Packaging @@ -14,17 +15,67 @@ To produce an installer, run the `package` target. To produce an executable installer on Windows, the following are required: -- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3 -- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c -- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 -- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0 -- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 -- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7 -- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in) -- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0 +1. [7-zip]() -Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. +1. [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.04 + Install using defaults (will install to `C:\Program Files (x86)\NSIS`) +1. [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c + 1. Extract Zip + 1. Copy `UAC.nsh` to `C:\Program Files (x86)\NSIS\Include\` + 1. Copy `Plugins\x86-ansi\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `Plugins\x86-unicode\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` +1. [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 (use the link marked **nsProcess_1_6.7z**) + 1. Extract Zip + 1. Copy `Include\nsProcess.nsh` to `C:\Program Files (x86)\NSIS\Include\` + 1. Copy `Plugins\nsProcess.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `Plugins\nsProcessW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` + +1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0 + 1. Extract Zip + 1. Copy `Plugin\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `Plugin\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` + +1. [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 + 1. Extract Zip + 1. Copy `NSISpre.nsh` to `C:\Program Files (x86)\NSIS\Include\` + 1. Copy `NSISpre.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + +1. [nsisSlideshow Plug-in for Nullsoft]() - 1.7 + 1. Extract Zip + 1. Copy `bin\nsisSlideshow.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `bin\nsisSlideshowW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` + +1. [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in) + 1. Download both Zips and unzip + 1. Copy `nsisunz\Release\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `NSISunzU\Plugin unicode\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` + +1. [ApplicationID plug-in for Nullsoft]() - 1.0 + 1. Download [`Pre-built DLLs`]() + 1. Extract Zip + 1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` + +1. [npm]() + 1. Install version 10.15.0 LTS + +1. Perform a clean cmake from a new terminal. +1. Open the `hifi.sln` Solution and select the Release configuration. +1. Build the Solution. +1. Build `packaged-server-console` (found under **Server Console**) + This will add 2 folders to `build\server-console\` - + `server-console-win32-x64` and `x64` +1. Build CMakeTargets->PACKAGE + Installer is now available in `build\_CPack_Packages\win64\NSIS` #### OS X - -Run the `package` target to create an Apple Disk Image (.dmg). +1. [npm]() + Install version 10.15.0 LTS + +1. Perform a clean cmake. +1. Perform a Release build of ALL_BUILD +1. Perform a Release build of `packaged-server-console` + This will add a folder to `build\server-console\` - + Sandbox-darwin-x64 +1. Perform a Release build of `package` + Installer is now available in `build/_CPack_Packages/Darwin/DragNDrop diff --git a/android/apps/framePlayer/CMakeLists.txt b/android/apps/framePlayer/CMakeLists.txt new file mode 100644 index 0000000000..327c2dd1bd --- /dev/null +++ b/android/apps/framePlayer/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME framePlayer) +setup_hifi_library(AndroidExtras) +link_hifi_libraries(shared ktx shaders qml gpu gl ${PLATFORM_GL_BACKEND}) +target_link_libraries(${TARGET_NAME} android log m) +target_opengl() diff --git a/android/apps/framePlayer/build.gradle b/android/apps/framePlayer/build.gradle new file mode 100644 index 0000000000..fc8651fce1 --- /dev/null +++ b/android/apps/framePlayer/build.gradle @@ -0,0 +1,50 @@ +apply plugin: 'com.android.application' + +android { + signingConfigs { + release { + keyAlias 'key0' + keyPassword 'password' + storeFile file('C:/android/keystore.jks') + storePassword 'password' + } + } + + compileSdkVersion 28 + defaultConfig { + applicationId "io.highfidelity.frameplayer" + minSdkVersion 25 + targetSdkVersion 28 + ndk { abiFilters 'arm64-v8a' } + externalNativeBuild { + cmake { + arguments '-DHIFI_ANDROID=1', + '-DHIFI_ANDROID_APP=framePlayer', + '-DANDROID_TOOLCHAIN=clang', + '-DANDROID_STL=c++_shared', + '-DCMAKE_VERBOSE_MAKEFILE=ON' + targets = ['framePlayer'] + } + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + } + + externalNativeBuild.cmake.path '../../../CMakeLists.txt' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs') + //implementation project(':oculus') + implementation project(':qt') +} diff --git a/android/apps/framePlayer/proguard-rules.pro b/android/apps/framePlayer/proguard-rules.pro new file mode 100644 index 0000000000..b3c0078513 --- /dev/null +++ b/android/apps/framePlayer/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/apps/framePlayer/src/main/AndroidManifest.xml b/android/apps/framePlayer/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..ed576387d2 --- /dev/null +++ b/android/apps/framePlayer/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/apps/framePlayer/src/main/cpp/FramePlayer.qrc b/android/apps/framePlayer/src/main/cpp/FramePlayer.qrc new file mode 100644 index 0000000000..44fdac2666 --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/FramePlayer.qrc @@ -0,0 +1,6 @@ + + + + qml/main.qml + + diff --git a/android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp b/android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp new file mode 100644 index 0000000000..7f0ec67639 --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp @@ -0,0 +1,91 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "PlayerWindow.h" + +#include +#include +#include +#include + +#include +#include + +PlayerWindow::PlayerWindow() { + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + setSurfaceType(QSurface::OpenGLSurface); + create(); + showFullScreen(); + + // Make sure the window has been created by processing events + QCoreApplication::processEvents(); + + // Start the rendering thread + _renderThread.initialize(this, &_surface); + + // Start the UI + _surface.resize(size()); + connect(&_surface, &hifi::qml::OffscreenSurface::rootContextCreated, this, [](QQmlContext* context){ + context->setContextProperty("FRAMES_FOLDER", "file:assets:/frames"); + }); + _surface.load("qrc:///qml/main.qml"); + + // Connect the UI handler + QObject::connect(_surface.getRootItem(), SIGNAL(loadFile(QString)), + this, SLOT(loadFile(QString)) + ); + + // Turn on UI input events + installEventFilter(&_surface); +} + +PlayerWindow::~PlayerWindow() { +} + +// static const char* FRAME_FILE = "assets:/frames/20190110_1635.json"; + +static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) { + QImage image; + QImageReader(filename.c_str()).read(&image); + if (layer > 0) { + return; + } + texture->assignStoredMip(0, image.byteCount(), image.constBits()); +} + +void PlayerWindow::loadFile(QString filename) { + QString realFilename = QUrl(filename).toLocalFile(); + if (QFileInfo(realFilename).exists()) { + auto frame = gpu::readFrame(realFilename.toStdString(), _renderThread._externalTexture, &textureLoader); + _surface.pause(); + _renderThread.submitFrame(frame); + } +} + +void PlayerWindow::touchEvent(QTouchEvent* event) { + // Super basic input handling when the 3D scene is active.... tap with two finders to return to the + // QML UI + static size_t touches = 0; + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + touches = std::max(touches, event->touchPoints().size()); + break; + + case QEvent::TouchEnd: + if (touches >= 2) { + _renderThread.submitFrame(nullptr); + _surface.resume(); + } + touches = 0; + break; + + default: + break; + } +} diff --git a/android/apps/framePlayer/src/main/cpp/PlayerWindow.h b/android/apps/framePlayer/src/main/cpp/PlayerWindow.h new file mode 100644 index 0000000000..b1cc7da5cd --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/PlayerWindow.h @@ -0,0 +1,35 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once +#include +#include + +#include +#include +#include "RenderThread.h" + +// Create a simple OpenGL window that renders text in various ways +class PlayerWindow : public QWindow { + Q_OBJECT + +public: + PlayerWindow(); + virtual ~PlayerWindow(); + +protected: + void touchEvent(QTouchEvent *ev) override; + +public slots: + void loadFile(QString filename); + +private: + hifi::qml::OffscreenSurface _surface; + QSettings _settings; + RenderThread _renderThread; +}; diff --git a/android/apps/framePlayer/src/main/cpp/RenderThread.cpp b/android/apps/framePlayer/src/main/cpp/RenderThread.cpp new file mode 100644 index 0000000000..76da789baa --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/RenderThread.cpp @@ -0,0 +1,162 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "RenderThread.h" + +#include + +void RenderThread::submitFrame(const gpu::FramePointer& frame) { + std::unique_lock lock(_frameLock); + _pendingFrames.push(frame); +} + +void RenderThread::move(const glm::vec3& v) { + std::unique_lock lock(_frameLock); + _correction = glm::inverse(glm::translate(mat4(), v)) * _correction; +} + +void RenderThread::setup() { + // Wait until the context has been moved to this thread + { std::unique_lock lock(_frameLock); } + + makeCurrent(); + // Disable vsync for profiling + ::gl::setSwapInterval(0); + + glClearColor(1, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glContext.swapBuffers(); + + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _gpuContext->beginFrame(); + _gpuContext->endFrame(); + makeCurrent(); + + + glGenFramebuffers(1, &_uiFbo); + glGenTextures(1, &_externalTexture); + glBindTexture(GL_TEXTURE_2D, _externalTexture); + static const glm::u8vec4 color{ 0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color); + + glClearColor(0, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glContext.swapBuffers(); +} + +void RenderThread::initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen) { + std::unique_lock lock(_frameLock); + setObjectName("RenderThread"); + Parent::initialize(); + + _offscreen = offscreen; + _window = window; + _glContext.setWindow(_window); + _glContext.create(); + _glContext.makeCurrent(); + + hifi::qml::OffscreenSurface::setSharedContext(_glContext.qglContext()); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glContext.swapBuffers(); + _glContext.doneCurrent(); + _glContext.moveToThread(_thread); + _thread->setObjectName("RenderThread"); +} + +void RenderThread::shutdown() { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _gpuContext->shutdown(); + _gpuContext.reset(); +} + +void RenderThread::renderFrame() { + auto windowSize = _window->geometry().size(); + uvec2 readFboSize; + uint32_t readFbo{ 0 }; + + if (_activeFrame) { + const auto &frame = _activeFrame; + _backend->recycle(); + _backend->syncCache(); + _gpuContext->enableStereo(frame->stereoState._enable); + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + } + auto &glBackend = static_cast(*_backend); + readFbo = glBackend.getFramebufferID(frame->framebuffer); + readFboSize = frame->framebuffer->getSize(); + CHECK_GL_ERROR(); + } else { + hifi::qml::OffscreenSurface::TextureAndFence newTextureAndFence; + if (_offscreen->fetchTexture(newTextureAndFence)) { + if (_uiTexture != 0) { + auto readFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _offscreen->getDiscardLambda()(_uiTexture, readFence); + _uiTexture = 0; + } + + glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)newTextureAndFence.second); + _uiTexture = newTextureAndFence.first; + glBindFramebuffer(GL_READ_FRAMEBUFFER, _uiFbo); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _uiTexture, 0); + } + + if (_uiTexture != 0) { + readFbo = _uiFbo; + readFboSize = { windowSize.width(), windowSize.height() }; + } + } + + if (readFbo) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + glBlitFramebuffer( + 0, 0, readFboSize.x, readFboSize.y, + 0, 0, windowSize.width(), windowSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } else { + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + _glContext.swapBuffers(); +} + +void RenderThread::updateFrame() { + std::queue pendingFrames; + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + } + + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + pendingFrames.pop(); + if (_activeFrame) { + _gpuContext->consumeFrameUpdates(_activeFrame); + } + } +} + +bool RenderThread::process() { + updateFrame(); + makeCurrent(); + renderFrame(); + return true; +} diff --git a/android/apps/framePlayer/src/main/cpp/RenderThread.h b/android/apps/framePlayer/src/main/cpp/RenderThread.h new file mode 100644 index 0000000000..c514874724 --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/RenderThread.h @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once + +#include + +#include +#include +#include + +class RenderThread : public GenericThread { + using Parent = GenericThread; +public: + QWindow* _window{ nullptr }; + std::mutex _mutex; + gpu::ContextPointer _gpuContext; // initialized during window creation + std::shared_ptr _backend; + std::atomic _presentCount{ 0 }; + std::mutex _frameLock; + std::queue _pendingFrames; + gpu::FramePointer _activeFrame; + uint32_t _externalTexture{ 0 }; + glm::mat4 _correction; + hifi::qml::OffscreenSurface* _offscreen{ nullptr }; + + gl::Context _glContext; + uint32_t _uiTexture{ 0 }; + uint32_t _uiFbo{ 0 }; + + void move(const glm::vec3& v); + void setup() override; + bool process() override; + void shutdown() override; + + void initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen); + + void submitFrame(const gpu::FramePointer& frame); + void updateFrame(); + void renderFrame(); + + bool makeCurrent() { + return _glContext.makeCurrent(); + } + + void doneCurrent() { + _glContext.doneCurrent(); + } +}; diff --git a/android/apps/framePlayer/src/main/cpp/main.cpp b/android/apps/framePlayer/src/main/cpp/main.cpp new file mode 100644 index 0000000000..3843583e5e --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/main.cpp @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2018/11/22 +// 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 + +#include +#include +#include + +#include + +#include "PlayerWindow.h" + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + if (!message.isEmpty()) { + const char * local=message.toStdString().c_str(); + switch (type) { + case QtDebugMsg: + __android_log_write(ANDROID_LOG_DEBUG,"Interface",local); + break; + case QtInfoMsg: + __android_log_write(ANDROID_LOG_INFO,"Interface",local); + break; + case QtWarningMsg: + __android_log_write(ANDROID_LOG_WARN,"Interface",local); + break; + case QtCriticalMsg: + __android_log_write(ANDROID_LOG_ERROR,"Interface",local); + break; + case QtFatalMsg: + default: + __android_log_write(ANDROID_LOG_FATAL,"Interface",local); + abort(); + } + } +} + +int main(int argc, char** argv) { + setupHifiApplication("gpuFramePlayer"); + QGuiApplication app(argc, argv); + auto oldMessageHandler = qInstallMessageHandler(messageHandler); + DependencyManager::set(); + PlayerWindow window; + app.exec(); + qInstallMessageHandler(oldMessageHandler); + return 0; +} + + diff --git a/android/apps/framePlayer/src/main/cpp/qml/main.qml b/android/apps/framePlayer/src/main/cpp/qml/main.qml new file mode 100644 index 0000000000..34c4507f9d --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/qml/main.qml @@ -0,0 +1,36 @@ +import QtQuick 2.2 +import QtQuick.Dialogs 1.1 +import Qt.labs.folderlistmodel 2.11 + +Item { + id: root + width: 640 + height: 480 + + ListView { + anchors.fill: parent + + FolderListModel { + id: folderModel + folder: FRAMES_FOLDER + nameFilters: ["*.json"] + } + + Component { + id: fileDelegate + Text { + text: fileName + font.pointSize: 36 + MouseArea { + anchors.fill: parent + onClicked: root.loadFile(folderModel.folder + "/" + fileName); + } + } + } + + model: folderModel + delegate: fileDelegate + } + + signal loadFile(string filename); +} diff --git a/android/apps/framePlayer/src/main/res/drawable/ic_launcher.xml b/android/apps/framePlayer/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000000..03b1edc4e9 --- /dev/null +++ b/android/apps/framePlayer/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/android/apps/framePlayer/src/main/res/values/strings.xml b/android/apps/framePlayer/src/main/res/values/strings.xml new file mode 100644 index 0000000000..8bf550f74e --- /dev/null +++ b/android/apps/framePlayer/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + GPU Frame Player + diff --git a/android/settings.gradle b/android/settings.gradle index 40b5eb44bf..1e7b3c768a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -3,3 +3,6 @@ project(':qt').projectDir = new File(settingsDir, 'libraries/qt') include ':interface' project(':interface').projectDir = new File(settingsDir, 'apps/interface') + +//include ':framePlayer' +//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer') diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 004e4ad2ea..bdec17bd8d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -68,6 +68,13 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : // hash the available codecs (on the mixer) _availableCodecs.clear(); // Make sure struct is clean auto pluginManager = DependencyManager::set(); + // Only load codec plugins; for now assume codec plugins have 'codec' in their name. + auto codecPluginFilter = [](const QJsonObject& metaData) { + QJsonValue nameValue = metaData["MetaData"]["name"]; + return nameValue.toString().contains("codec", Qt::CaseInsensitive); + }; + pluginManager->setPluginFilter(codecPluginFilter); + auto codecPlugins = pluginManager->getCodecPlugins(); for_each(codecPlugins.cbegin(), codecPlugins.cend(), [&](const CodecPluginPointer& codec) { diff --git a/cmake/macros/TargetGlad.cmake b/cmake/macros/TargetGlad.cmake index 929f61c3f2..c9a2529986 100644 --- a/cmake/macros/TargetGlad.cmake +++ b/cmake/macros/TargetGlad.cmake @@ -7,6 +7,7 @@ # macro(TARGET_GLAD) if (ANDROID) + include(SelectLibraryConfigurations) set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/glad) set(GLAD_INCLUDE_DIRS "${INSTALL_DIR}/include") set(GLAD_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libglad_d.a) @@ -31,8 +32,8 @@ macro(TARGET_GLAD) set(GLAD_INCLUDE_DIRS ${${GLAD_UPPER}_INCLUDE_DIRS}) set(GLAD_LIBRARY ${${GLAD_UPPER}_LIBRARY}) endif() - + target_include_directories(${TARGET_NAME} PUBLIC ${GLAD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${GLAD_LIBRARY}) - target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES}) + target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES}) endmacro() diff --git a/cmake/macros/TargetZlib.cmake b/cmake/macros/TargetZlib.cmake index 79dce01a3d..172e5bd13b 100644 --- a/cmake/macros/TargetZlib.cmake +++ b/cmake/macros/TargetZlib.cmake @@ -6,8 +6,13 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_ZLIB) - # using VCPKG for zlib - find_package(ZLIB REQUIRED) - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${ZLIB_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) + if (ANDROID) + # zlib is part of the NDK + target_link_libraries(${TARGET_NAME} z) + else() + # using VCPKG for zlib + find_package(ZLIB REQUIRED) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) + endif() endmacro() diff --git a/cmake/ports/nvtt/CONTROL b/cmake/ports/nvtt/CONTROL index 59ba36d830..94d6193116 100644 --- a/cmake/ports/nvtt/CONTROL +++ b/cmake/ports/nvtt/CONTROL @@ -1,3 +1,3 @@ Source: nvtt -Version: 8c7e6b40ee5095f227b75880fabd89c99d6f34c0 +Version: 330c4d56274a0f602a5c70596e2eb670a4ed56c2 Description: Texture processing tools with support for Direct3D 10 and 11 formats. \ No newline at end of file diff --git a/cmake/ports/nvtt/portfile.cmake b/cmake/ports/nvtt/portfile.cmake index 13b1253322..4cbe05b692 100644 --- a/cmake/ports/nvtt/portfile.cmake +++ b/cmake/ports/nvtt/portfile.cmake @@ -10,8 +10,8 @@ include(vcpkg_common_functions) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO highfidelity/nvidia-texture-tools - REF 8c7e6b40ee5095f227b75880fabd89c99d6f34c0 - SHA512 f107d19dbbd6651ef2126b1422a5db8db291bf70311ac4fb1dbacb5ceaa8752fee38becbd32964f57596f0b84e1223bb2c3ff9d9c4fdc65c3e77a47836657cef + REF 330c4d56274a0f602a5c70596e2eb670a4ed56c2 + SHA512 4c0bc2f369120d696cc27710b6d33086b27eef55f537ec66b9a5c8b1839bc2426c0413670b0f65be52c5d353468f0126dfe024be1f0690611d4d7e33ac530127 HEAD_REF master ) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index b3ba74a985..6c879aca70 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -246,6 +246,7 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) { if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) { qCWarning(asset_backup) << "Current mappings not yet loaded."; + _backups.emplace_back(backupName, AssetUtils::Mappings(), true); return; } diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 0f3db48925..2d5c68c0e8 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -54,7 +54,8 @@ FocusScope { Image { z: -10 id: loginDialogBackground - source: "LoginDialog/images/background.jpg" + fillMode: Image.PreserveAspectCrop + source: "LoginDialog/images/background.png" anchors.fill: parent } @@ -119,6 +120,6 @@ FocusScope { } Component.onCompleted: { - bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); + bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false }); } } diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index 144b91063f..ebc677fb00 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -22,11 +22,16 @@ Item { width: root.width height: root.height readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + readonly property string termsContainerOculusText: qsTr("By signing up, you agree to High Fidelity's Terms of Service") + readonly property int textFieldHeight: 31 readonly property string fontFamily: "Raleway" readonly property int fontSize: 15 readonly property bool fontBold: true + readonly property int textFieldFontSize: 18 + readonly property var passwordImageRatio: 16 / 23 - readonly property bool withSteam: withSteam + property bool withOculus: withOculus + property bool withSteam: withSteam property string errorString: errorString readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -61,15 +66,20 @@ Item { Item { id: contentItem - anchors.fill: parent + width: parent.width + height: errorContainer.height + fields.height + buttons.height + additionalTextContainer.height + + termsContainer.height + anchors.top: parent.top + anchors.topMargin: root.bannerHeight + 0.25 * parent.height + anchors.left: parent.left Item { id: errorContainer - width: parent.width + width: root.bannerWidth height: loginErrorMessageTextMetrics.height anchors { - bottom: buttons.top; - bottomMargin: hifi.dimensions.contentSpacing.y; + bottom: completeProfileBody.withOculus ? fields.top : buttons.top; + bottomMargin: 1.5 * hifi.dimensions.contentSpacing.y; left: buttons.left; } TextMetrics { @@ -79,8 +89,8 @@ Item { } Text { id: loginErrorMessage; - width: root.bannerWidth color: "red"; + width: root.bannerWidth; font.family: completeProfileBody.fontFamily font.pixelSize: 18 font.bold: completeProfileBody.fontBold @@ -88,13 +98,196 @@ Item { horizontalAlignment: Text.AlignHCenter text: completeProfileBody.errorString visible: true + onTextChanged: { + mainContainer.recalculateErrorMessage(); + } + Component.onCompleted: { + mainContainer.recalculateErrorMessage(); + } } - Component.onCompleted: { - if (loginErrorMessageTextMetrics.width > root.bannerWidth && root.isTablet) { - loginErrorMessage.wrapMode = Text.WordWrap; - loginErrorMessage.verticalAlignment = Text.AlignLeft; - loginErrorMessage.horizontalAlignment = Text.AlignLeft; - errorContainer.height = 3 * loginErrorMessageTextMetrics.height; + } + + Item { + id: fields + width: root.bannerWidth + height: 3 * completeProfileBody.textFieldHeight + 2 * hifi.dimensions.contentSpacing.y + visible: completeProfileBody.withOculus + anchors { + left: parent.left + leftMargin: (parent.width - root.bannerWidth) / 2 + bottom: buttons.top + bottomMargin: hifi.dimensions.contentSpacing.y + } + + HifiControlsUit.TextField { + id: usernameField + width: root.bannerWidth + height: completeProfileBody.textFieldHeight + placeholderText: "Username" + font.pixelSize: completeProfileBody.textFieldFontSize + styleRenderType: Text.QtRendering + anchors { + top: parent.top + } + Keys.onPressed: { + if (!usernameField.visible) { + return; + } + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + if (event.modifiers === Qt.ShiftModifier) { + passwordField.focus = true; + } else { + emailField.focus = true; + } + break; + case Qt.Key_Backtab: + event.accepted = true; + passwordField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + break; + } + } + onFocusChanged: { + root.text = ""; + if (focus) { + root.isPassword = false; + } + } + Component.onCompleted: { + var userID = ""; + if (completeProfileBody.withOculus) { + userID = loginDialog.oculusUserID(); + } + usernameField.text = userID; + } + } + HifiControlsUit.TextField { + id: emailField + width: root.bannerWidth + height: completeProfileBody.textFieldHeight + anchors { + top: usernameField.bottom + topMargin: hifi.dimensions.contentSpacing.y + } + placeholderText: "Email" + font.pixelSize: completeProfileBody.textFieldFontSize + styleRenderType: Text.QtRendering + activeFocusOnPress: true + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + if (event.modifiers === Qt.ShiftModifier) { + usernameField.focus = true; + } else { + passwordField.focus = true; + } + break; + case Qt.Key_Backtab: + event.accepted = true; + usernameField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + break; + } + } + onFocusChanged: { + root.text = ""; + if (focus) { + root.isPassword = false; + } + } + } + HifiControlsUit.TextField { + id: passwordField + width: root.bannerWidth + height: completeProfileBody.textFieldHeight + placeholderText: "Password (optional)" + font.pixelSize: completeProfileBody.textFieldFontSize + styleRenderType: Text.QtRendering + activeFocusOnPress: true + echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password + anchors { + top: emailField.bottom + topMargin: hifi.dimensions.contentSpacing.y + } + + onFocusChanged: { + root.text = ""; + root.isPassword = focus; + } + + Item { + id: showPasswordContainer + z: 10 + // width + image's rightMargin + width: showPasswordImage.width + 8 + height: parent.height + anchors { + right: parent.right + } + + Image { + id: showPasswordImage + width: passwordField.height * passwordImageRatio + height: passwordField.height * passwordImageRatio + anchors { + right: parent.right + rightMargin: 8 + top: parent.top + topMargin: passwordFieldMouseArea.showPassword ? 6 : 8 + bottom: parent.bottom + bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8 + } + source: passwordFieldMouseArea.showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg" + MouseArea { + id: passwordFieldMouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + property bool showPassword: false + onClicked: { + showPassword = !showPassword; + } + } + } + } + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Tab: + event.accepted = true; + if (event.modifiers === Qt.ShiftModifier) { + emailField.focus = true; + } else if (usernameField.visible) { + usernameField.focus = true; + } else { + emailField.focus = true; + } + break; + case Qt.Key_Backtab: + event.accepted = true; + emailField.focus = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + event.accepted = true; + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + break; + } } } } @@ -105,7 +298,7 @@ Item { height: d.minHeightButton anchors { top: parent.top - topMargin: (parent.height - additionalTextContainer.height) / 2 - hifi.dimensions.contentSpacing.y + topMargin: (parent.height - additionalTextContainer.height + fields.height) / 2 - hifi.dimensions.contentSpacing.y left: parent.left leftMargin: (parent.width - root.bannerWidth) / 2 } @@ -144,7 +337,7 @@ Item { width: (parent.width - hifi.dimensions.contentSpacing.x) / 2 height: d.minHeightButton - text: qsTr("Create your profile") + text: completeProfileBody.withOculus ? qsTr("Sign Up") : qsTr("Create your profile") color: hifi.buttons.blue fontFamily: completeProfileBody.fontFamily @@ -158,55 +351,12 @@ Item { UserActivityLogger.logAction("encourageLoginDialog", data); } loginErrorMessage.visible = false; - loginDialog.createAccountFromSteam(); - } - } - } - - Item { - id: additionalTextContainer - width: parent.width - height: additionalTextMetrics.height - anchors { - top: buttons.bottom - horizontalCenter: parent.horizontalCenter - topMargin: hifi.dimensions.contentSpacing.y - left: parent.left - } - - TextMetrics { - id: additionalTextMetrics - font: additionalText.font - text: "Already have a High Fidelity profile? Link to an existing profile here." - } - - HifiStylesUit.ShortcutText { - id: additionalText - text: "Already have a High Fidelity profile? Link to an existing profile here." - - font.family: completeProfileBody.fontFamily - font.pixelSize: completeProfileBody.fontSize - font.bold: completeProfileBody.fontBold - wrapMode: Text.NoWrap - lineHeight: 1 - lineHeightMode: Text.ProportionalHeight - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - - onLinkActivated: { - loginDialog.isLogIn = true; - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "withSteam": true, "linkSteam": true }); - } - Component.onCompleted: { - if (additionalTextMetrics.width > root.bannerWidth && root.isTablet) { - additionalText.width = root.bannerWidth; - additionalText.wrapMode = Text.WordWrap; - additionalText.verticalAlignment = Text.AlignLeft; - additionalText.horizontalAlignment = Text.AlignLeft; - additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height; - additionalTextContainer.anchors.left = buttons.left; - } else { - additionalText.anchors.centerIn = additionalTextContainer; + if (completeProfileBody.withOculus) { + loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true }); + } else if (completeProfileBody.withSteam) { + loginDialog.createAccountFromSteam(); } } } @@ -217,29 +367,33 @@ Item { width: parent.width height: termsTextMetrics.height anchors { - top: additionalTextContainer.bottom + top: buttons.bottom horizontalCenter: parent.horizontalCenter - topMargin: 2 * hifi.dimensions.contentSpacing.y + topMargin: hifi.dimensions.contentSpacing.y left: parent.left } TextMetrics { id: termsTextMetrics font: termsText.font - text: completeProfileBody.termsContainerText + text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText Component.onCompleted: { // with the link. - termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + if (completeProfileBody.withOculus) { + termsText.text = qsTr("By signing up, you agree to High Fidelity's Terms of Service") + } else { + termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + } } } HifiStylesUit.InfoItem { id: termsText - text: completeProfileBody.termsContainerText + text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText font.family: completeProfileBody.fontFamily font.pixelSize: completeProfileBody.fontSize font.bold: completeProfileBody.fontBold wrapMode: Text.WordWrap - color: hifi.colors.lightGray + color: hifi.colors.white linkColor: hifi.colors.blueAccent lineHeight: 1 lineHeightMode: Text.ProportionalHeight @@ -247,7 +401,7 @@ Item { onLinkActivated: loginDialog.openUrl(link); Component.onCompleted: { - if (termsTextMetrics.width > root.bannerWidth && root.isTablet) { + if (termsTextMetrics.width > root.bannerWidth) { termsText.width = root.bannerWidth; termsText.wrapMode = Text.WordWrap; additionalText.verticalAlignment = Text.AlignLeft; @@ -260,14 +414,86 @@ Item { } } } + + Item { + id: additionalTextContainer + width: parent.width + height: additionalTextMetrics.height + anchors { + top: termsContainer.bottom + horizontalCenter: parent.horizontalCenter + topMargin: 2 * hifi.dimensions.contentSpacing.y + left: parent.left + } + + TextMetrics { + id: additionalTextMetrics + font: additionalText.font + text: "Already have a High Fidelity profile? Link to an existing profile here." + } + + HifiStylesUit.ShortcutText { + id: additionalText + text: "Already have a High Fidelity profile? Link to an existing profile here." + width: root.bannerWidth; + font.family: completeProfileBody.fontFamily + font.pixelSize: completeProfileBody.fontSize + font.bold: completeProfileBody.fontBold + wrapMode: Text.NoWrap + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + linkColor: hifi.colors.blueAccent + + onLinkActivated: { + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", + "withSteam": completeProfileBody.withSteam, "linkSteam": completeProfileBody.withSteam, "withOculus": completeProfileBody.withOculus, + "linkOculus": completeProfileBody.withOculus }); + } + Component.onCompleted: { + if (additionalTextMetrics.width > root.bannerWidth) { + additionalText.wrapMode = Text.WordWrap; + additionalText.verticalAlignment = Text.AlignLeft; + additionalText.horizontalAlignment = Text.AlignLeft; + additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height; + additionalTextContainer.anchors.left = buttons.left; + } else { + additionalText.anchors.centerIn = additionalTextContainer; + } + } + } + } + } + function recalculateErrorMessage() { + if (completeProfileBody.errorString !== "") { + loginErrorMessage.visible = true; + var errorLength = completeProfileBody.errorString.split(/\r\n|\r|\n/).length; + var errorStringEdited = completeProfileBody.errorString.replace(/[\n\r]+/g, "\n"); + loginErrorMessage.text = errorStringEdited; + if (errorLength > 1.0) { + loginErrorMessage.wrapMode = Text.WordWrap; + loginErrorMessage.verticalAlignment = Text.AlignLeft; + loginErrorMessage.horizontalAlignment = Text.AlignLeft; + errorContainer.height = errorLength * loginErrorMessageTextMetrics.height; + } else if (loginErrorMessageTextMetrics.width > root.bannerWidth) { + loginErrorMessage.wrapMode = Text.WordWrap; + loginErrorMessage.verticalAlignment = Text.AlignLeft; + loginErrorMessage.horizontalAlignment = Text.AlignLeft; + errorContainer.height = (loginErrorMessageTextMetrics.width / root.bannerWidth) * loginErrorMessageTextMetrics.height; + } else { + loginErrorMessage.wrapMode = Text.NoWrap; + loginErrorMessage.verticalAlignment = Text.AlignVCenter; + loginErrorMessage.horizontalAlignment = Text.AlignHCenter; + errorContainer.height = loginErrorMessageTextMetrics.height; + } + } } } Connections { target: loginDialog onHandleCreateCompleted: { - console.log("Create Succeeded") - + console.log("Create Succeeded"); if (completeProfileBody.withSteam) { if (completeProfileBody.loginDialogPoppedUp) { var data = { @@ -277,20 +503,24 @@ Item { } loginDialog.loginThroughSteam(); } - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false }); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false, + "withOculus": completeProfileBody.withOculus, "linkOculus": false }); } onHandleCreateFailed: { console.log("Create Failed: " + error); - if (completeProfileBody.withSteam) { + if (completeProfileBody.withSteam || completeProfileBody.withOculus) { if (completeProfileBody.loginDialogPoppedUp) { + action = completeProfileBody.withSteam ? "Steam" : "Oculus"; var data = { - "action": "user failed to create a profile with Steam from the complete profile screen" + "action": "user failed to create a profile with " + action + " from the complete profile screen" } UserActivityLogger.logAction("encourageLoginDialog", data); } } - - bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam }); + if (!completeProfileBody.withOculus) { + bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, + "withOculus": completeProfileBody.withOculus }); + } } } @@ -302,5 +532,6 @@ Item { } d.resize(); root.text = ""; + usernameField.forceActiveFocus(); } } diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 5048bf0278..4dd05f594d 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -36,9 +36,10 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false - property bool withSteam: false + property bool withSteam: withSteam property bool linkSteam: linkSteam - property bool withOculus: false + property bool withOculus: withOculus + property bool linkOculus: linkOculus property string errorString: errorString property bool lostFocus: false @@ -83,23 +84,24 @@ Item { } UserActivityLogger.logAction("encourageLoginDialog", data); } - - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, + "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus }); } function init() { // going to/from sign in/up dialog. - loginDialog.isLogIn = true; loginErrorMessage.text = linkAccountBody.errorString; loginErrorMessage.visible = (linkAccountBody.errorString !== ""); - loginButton.text = !linkAccountBody.linkSteam ? "Log In" : "Link Account"; + if (loginErrorMessageTextMetrics.width > emailField.width) { + loginErrorMessage.wrapMode = Text.WordWrap; + errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height; + } + loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.color = hifi.buttons.blue; emailField.placeholderText = "Username or Email"; var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; - if (linkAccountBody.linkSteam) { - steamInfoText.anchors.top = passwordField.bottom; - keepMeLoggedInCheckbox.anchors.top = steamInfoText.bottom; + if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2; loginButton.anchors.right = emailField.right; } else { @@ -125,7 +127,7 @@ Item { id: loginContainer width: emailField.width height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + - keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height + steamInfoTextMetrics.height + keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height anchors { top: parent.top topMargin: root.bannerHeight + 0.25 * parent.height @@ -135,7 +137,7 @@ Item { Item { id: errorContainer - width: loginErrorMessageTextMetrics.width + width: parent.width height: loginErrorMessageTextMetrics.height anchors { bottom: emailField.top; @@ -304,7 +306,7 @@ Item { fontSize: linkAccountBody.fontSize fontBold: linkAccountBody.fontBold color: hifi.buttons.noneBorderlessWhite; - visible: linkAccountBody.linkSteam + visible: linkAccountBody.linkSteam || linkAccountBody.linkOculus anchors { top: keepMeLoggedInCheckbox.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -315,10 +317,9 @@ Item { "action": "user clicked cancel at link account screen" }; UserActivityLogger.logAction("encourageLoginDialog", data); - loginDialog.dismissLoginDialog(); } - - bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" }); + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, + "withOculus": linkAccountBody.withOculus, "errorString": "" }); } } HifiControlsUit.Button { @@ -337,33 +338,6 @@ Item { linkAccountBody.login(); } } - TextMetrics { - id: steamInfoTextMetrics - font: steamInfoText.font - text: steamInfoText.text - } - Text { - id: steamInfoText - width: root.bannerWidth - visible: linkAccountBody.linkSteam - anchors { - top: loginButton.bottom - topMargin: hifi.dimensions.contentSpacing.y - left: emailField.left - } - - font.family: linkAccountBody.fontFamily - font.pixelSize: linkAccountBody.textFieldFontSize - color: "white" - text: qsTr("Your Steam account information will not be exposed to others."); - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - Component.onCompleted: { - if (steamInfoTextMetrics.width > root.bannerWidth) { - steamInfoText.wrapMode = Text.WordWrap; - } - } - } TextMetrics { id: cantAccessTextMetrics font: cantAccessText.font @@ -372,7 +346,7 @@ Item { HifiStylesUit.ShortcutText { id: cantAccessText z: 10 - visible: !linkAccountBody.linkSteam + visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus anchors { top: loginButton.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -423,10 +397,10 @@ Item { buttonGlyphSize: 24 buttonGlyphRightMargin: 10 onClicked: { - // if (loginDialog.isOculusStoreRunning()) { - // linkAccountBody.withOculus = true; - // loginDialog.loginThroughSteam(); - // } else + if (loginDialog.isOculusRunning()) { + linkAccountBody.withOculus = true; + loginDialog.loginThroughOculus(); + } else if (loginDialog.isSteamRunning()) { linkAccountBody.withSteam = true; loginDialog.loginThroughSteam(); @@ -446,18 +420,17 @@ Item { } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, - "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); + "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus }); } Component.onCompleted: { - if (linkAccountBody.linkSteam) { + if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { continueButton.visible = false; return; } - // if (loginDialog.isOculusStoreRunning()) { - // continueButton.text = qsTr("CONTINUE WITH OCULUS"); - // continueButton.buttonGlyph = hifi.glyphs.oculus; - // } else - if (loginDialog.isSteamRunning()) { + if (loginDialog.isOculusRunning()) { + continueButton.text = qsTr("CONTINUE WITH OCULUS"); + continueButton.buttonGlyph = hifi.glyphs.oculus; + } else if (loginDialog.isSteamRunning()) { continueButton.text = qsTr("CONTINUE WITH STEAM"); continueButton.buttonGlyph = hifi.glyphs.steamSquare; } else { @@ -470,7 +443,7 @@ Item { id: signUpContainer width: loginContainer.width height: signUpTextMetrics.height - visible: !linkAccountBody.linkSteam + visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus anchors { left: loginContainer.left top: loginContainer.bottom @@ -519,7 +492,7 @@ Item { UserActivityLogger.logAction("encourageLoginDialog", data); } bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, - "errorString": "", "linkSteam": linkAccountBody.linkSteam }); + "errorString": "" }); } } } @@ -543,7 +516,7 @@ Item { fontFamily: linkAccountBody.fontFamily fontSize: linkAccountBody.fontSize fontBold: linkAccountBody.fontBold - visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam; + visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus; onClicked: { if (linkAccountBody.loginDialogPoppedUp) { var data = { diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 5e4a6c4cb3..583f00583b 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -29,6 +29,8 @@ Item { property bool withSteam: withSteam property bool withOculus: withOculus property bool linkSteam: linkSteam + property bool linkOculus: linkOculus + property bool createOculus: createOculus readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -75,15 +77,25 @@ Item { } } + Timer { + id: oculusSuccessTimer + interval: 500; + running: false; + repeat: false; + onTriggered: { + loginDialog.loginThroughOculus(); + init(); + } + } + function init() { // For the process of logging in. loggingInText.wrapMode = Text.NoWrap; - - if (loggingInBody.linkSteam) { + if (loggingInBody.createOculus) { + loggingInGlyph.text = hifi.glyphs.oculus; loggingInGlyph.visible = true; - loggingInText.text = "Linking to Steam"; + loggingInText.text = "Creating account with Oculus"; loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; - loginDialog.linkSteam(); } else if (loggingInBody.withSteam) { loggingInGlyph.visible = true; loggingInText.text = "Logging in to Steam"; @@ -100,12 +112,18 @@ Item { loggingInSpinner.visible = true; } function loadingSuccess() { - loggingInSpinner.visible = false; if (loggingInBody.linkSteam) { loggingInText.text = "Linking to Steam"; + loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; loginDialog.linkSteam(); return; + } else if (loggingInBody.linkOculus) { + loggingInText.text = "Linking to Oculus"; + loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; + loginDialog.linkOculus(); + return; } + loggingInSpinner.visible = false; if (loggingInBody.withSteam) { // reset the flag. loggingInGlyph.visible = false; @@ -246,6 +264,26 @@ Item { verticalAlignment: Text.AlignVCenter; visible: false; } + HifiControlsUit.Button { + id: okButton; + width: d.minWidthButton + height: d.minHeightButton + text: qsTr("OK") + color: hifi.buttons.white + anchors { + top: loggedInGlyph.bottom + topMargin: 3 * hifi.dimensions.contentSpacing.y + left: parent.left + leftMargin: (parent.width - width) / 2; + } + onClicked: { + root.tryDestroy(); + if (loginDialog.getLoginDialogPoppedUp()) { + loginDialog.dismissLoginDialog(); + } + } + visible: false + } } } } @@ -257,6 +295,34 @@ Item { Connections { target: loginDialog + onHandleCreateCompleted: { + console.log("Create Succeeded") + if (loggingInBody.withOculus) { + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user created Oculus account successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loggingInBody.createOculus = false; + loggingInText.text = "Account created!"; + loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; + oculusSuccessTimer.start(); + } + } + onHandleCreateFailed: { + console.log("Create Failed: " + error); + if (loggingInBody.withOculus) { + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user created Oculus account unsuccessfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "errorString": error }); + } + } onHandleLinkCompleted: { console.log("Link Succeeded"); if (loggingInBody.linkSteam) { @@ -267,21 +333,40 @@ Item { }; UserActivityLogger.logAction("encourageLoginDialog", data); } - - loggingInBody.loadingSuccess(); + } else if (loggingInBody.linkOculus) { + loggingInBody.linkOculus = false; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Oculus with their hifi account credentials successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } + loggingInBody.loadingSuccess(); } onHandleLinkFailed: { console.log("Link Failed: " + error); - if (loggingInBody.linkSteam) { + loggingInSpinner.visible = false; + if (loggingInBody.linkOculus) { + loggingInText.text = "Oculus failed to link"; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Oculus unsuccessfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + okButton.visible = true; + } else if (loggingInBody.linkSteam){ if (loggingInBody.loginDialogPoppedUp) { var data = { "action": "user linked Steam unsuccessfully" }; UserActivityLogger.logAction("encourageLoginDialog", data); } + } else { + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": loggingInBody.linkSteam, + "linkOculus": loggingInBody.linkOculus, "errorString": error }); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error }); } onHandleLoginCompleted: { @@ -292,8 +377,19 @@ Item { onHandleLoginFailed: { console.log("Login Failed") loggingInSpinner.visible = false; + loggingInGlyph.visible = false; var errorString = ""; - if (loggingInBody.linkSteam && loggingInBody.withSteam) { + if (loggingInBody.linkOculus && loggingInBody.withOculus) { + errorString = "Username or password is incorrect."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to link Oculus with their hifi account credentials" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); + } else if (loggingInBody.linkSteam && loggingInBody.withSteam) { errorString = "Username or password is incorrect."; if (loggingInBody.loginDialogPoppedUp) { var data = { @@ -301,9 +397,9 @@ Item { }; UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString }); + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); } else if (loggingInBody.withSteam) { - loggingInGlyph.visible = false; errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again."; if (loggingInBody.loginDialogPoppedUp) { var data = { @@ -311,19 +407,19 @@ Item { }; UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString }); + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); } else if (loggingInBody.withOculus) { - loggingInGlyph.visible = false; - errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again." + errorString = "Your Oculus account is not connected to an existing High Fidelity account. Please create a new one." if (loggingInBody.loginDialogPoppedUp) { var data = { "action": "user failed to authenticate with Oculus to log in" }; UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString }); - } - else { + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, + "withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString }); + } else { errorString = "Username or password is incorrect."; if (loggingInBody.loginDialogPoppedUp) { var data = { diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 3ba66391e6..64df9089a1 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -23,6 +23,7 @@ Item { clip: true height: root.height width: root.width + readonly property string termsContainerText: qsTr("By signing up, you agree to High Fidelity's Terms of Service") property int textFieldHeight: 31 property string fontFamily: "Raleway" property int fontSize: 15 @@ -37,7 +38,6 @@ Item { onKeyboardRaisedChanged: d.resize(); property string errorString: errorString - property bool linkSteam: linkSteam property bool lostFocus: false readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() @@ -73,7 +73,6 @@ Item { function init() { // going to/from sign in/up dialog. - loginDialog.isLogIn = false; emailField.placeholderText = "Email"; emailField.text = ""; emailField.anchors.top = usernameField.bottom; @@ -353,7 +352,7 @@ Item { } UserActivityLogger.logAction("encourageLoginDialog", data); } - bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam }); + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); } } HifiControlsUit.Button { @@ -380,6 +379,54 @@ Item { signUpBody.signup(); } } + Item { + id: termsContainer + width: parent.width + height: termsTextMetrics.height + anchors { + top: signUpButton.bottom + horizontalCenter: parent.horizontalCenter + topMargin: 2 * hifi.dimensions.contentSpacing.y + left: parent.left + } + TextMetrics { + id: termsTextMetrics + font: termsText.font + text: signUpBody.termsContainerText + Component.onCompleted: { + // with the link. + termsText.text = qsTr("By signing up, you agree to High Fidelity's Terms of Service") + } + } + + HifiStylesUit.InfoItem { + id: termsText + text: signUpBody.termsContainerText + font.family: signUpBody.fontFamily + font.pixelSize: signUpBody.fontSize + font.bold: signUpBody.fontBold + wrapMode: Text.WordWrap + color: hifi.colors.white + linkColor: hifi.colors.blueAccent + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + + onLinkActivated: loginDialog.openUrl(link); + + Component.onCompleted: { + if (termsTextMetrics.width > root.bannerWidth) { + termsText.width = root.bannerWidth; + termsText.wrapMode = Text.WordWrap; + additionalText.verticalAlignment = Text.AlignLeft; + additionalText.horizontalAlignment = Text.AlignLeft; + termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height; + termsContainer.anchors.left = buttons.left; + } else { + termsText.anchors.centerIn = termsContainer; + } + } + } + } } } @@ -433,14 +480,15 @@ Item { if (errorString !== "") { loginErrorMessage.visible = true; + var errorLength = errorString.split(/\r\n|\r|\n/).length; var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n"); loginErrorMessage.text = errorStringEdited; - loginErrorMessageTextMetrics.text = errorString; - if (loginErrorMessageTextMetrics.width > usernameField.width) { + if (errorLength > 1.0) { + loginErrorMessage.width = root.bannerWidth; loginErrorMessage.wrapMode = Text.WordWrap; loginErrorMessage.verticalAlignment = Text.AlignLeft; loginErrorMessage.horizontalAlignment = Text.AlignLeft; - errorContainer.height = (loginErrorMessageTextMetrics.width / usernameField.width) * loginErrorMessageTextMetrics.height; + errorContainer.height = errorLength * loginErrorMessageTextMetrics.height; } errorContainer.anchors.bottom = usernameField.top; errorContainer.anchors.bottomMargin = hifi.dimensions.contentSpacing.y; diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index af46fc0223..2c8e61a29a 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -19,6 +19,7 @@ import TabletScriptingInterface 1.0 Item { id: usernameCollisionBody clip: true + readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") width: root.width height: root.height readonly property string fontFamily: "Raleway" @@ -26,13 +27,18 @@ Item { readonly property int textFieldFontSize: 18 readonly property bool fontBold: true - readonly property bool withSteam: withSteam + property bool withSteam: withSteam + property bool withOculus: withOculus readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() function create() { mainTextContainer.visible = false - loginDialog.createAccountFromSteam(textField.text); + if (usernameCollisionBody.withOculus) { + loginDialog.createAccountFromOculus(textField.text); + } else if (usernameCollisionBody.withSteam) { + loginDialog.createAccountFromSteam(textField.text); + } } property bool keyboardEnabled: false @@ -90,12 +96,19 @@ Item { font.family: usernameCollisionBody.fontFamily font.pixelSize: usernameCollisionBody.fontSize font.bold: usernameCollisionBody.fontBold - text: qsTr("Your Steam username is not available."); + text: qsTr(""); wrapMode: Text.WordWrap color: hifi.colors.redAccent lineHeight: 1 lineHeightMode: Text.ProportionalHeight horizontalAlignment: Text.AlignHCenter + Component.onCompleted: { + if (usernameCollisionBody.withOculus) { + text = qsTr("Your Oculus username is not available."); + } else if (usernameCollisionBody.withSteam) { + text = qsTr("Your Steam username is not available."); + } + } } @@ -164,7 +177,8 @@ Item { fontSize: usernameCollisionBody.fontSize fontBold: usernameCollisionBody.fontBold onClicked: { - bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "" }); + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, + "withOculus": usernameCollisionBody.withOculus, "errorString": "" }); } } HifiControlsUit.Button { @@ -187,6 +201,55 @@ Item { } } } + Item { + id: termsContainer + width: parent.width + height: termsTextMetrics.height + anchors { + top: buttons.bottom + horizontalCenter: parent.horizontalCenter + topMargin: 2 * hifi.dimensions.contentSpacing.y + left: parent.left + leftMargin: (parent.width - buttons.width) / 2 + } + TextMetrics { + id: termsTextMetrics + font: termsText.font + text: usernameCollisionBody.termsContainerText + Component.onCompleted: { + // with the link. + termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + } + } + + HifiStylesUit.InfoItem { + id: termsText + text: usernameCollisionBody.termsContainerText + font.family: usernameCollisionBody.fontFamily + font.pixelSize: usernameCollisionBody.fontSize + font.bold: usernameCollisionBody.fontBold + wrapMode: Text.WordWrap + color: hifi.colors.white + linkColor: hifi.colors.blueAccent + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + + onLinkActivated: loginDialog.openUrl(link); + + Component.onCompleted: { + if (termsTextMetrics.width > root.bannerWidth) { + termsText.width = root.bannerWidth; + termsText.wrapMode = Text.WordWrap; + additionalText.verticalAlignment = Text.AlignLeft; + additionalText.horizontalAlignment = Text.AlignLeft; + termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height; + termsContainer.anchors.left = buttons.left; + } else { + termsText.anchors.centerIn = termsContainer; + } + } + } + } } Component.onCompleted: { @@ -201,18 +264,25 @@ Item { target: loginDialog onHandleCreateCompleted: { console.log("Create Succeeded"); - if (usernameCollisionBody.withSteam) { + if (usernameCollisionBody.withOculus) { + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user created a profile with Oculus successfully in the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loginDialog.loginThroughOculus(); + } else if (usernameCollisionBody.withSteam) { if (usernameCollisionBody.loginDialogPoppedUp) { var data = { "action": "user created a profile with Steam successfully in the username collision screen" } UserActivityLogger.logAction("encourageLoginDialog", data); } - loginDialog.loginThroughSteam(); } - - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false }) + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, + "withOculus": usernameCollisionBody.withOculus, "linkSteam": false, "linkOculus": false }) } onHandleCreateFailed: { console.log("Create Failed: " + error) diff --git a/interface/resources/qml/LoginDialog/images/background.jpg b/interface/resources/qml/LoginDialog/images/background.jpg deleted file mode 100644 index db2798a4a1..0000000000 Binary files a/interface/resources/qml/LoginDialog/images/background.jpg and /dev/null differ diff --git a/interface/resources/qml/LoginDialog/images/background.png b/interface/resources/qml/LoginDialog/images/background.png new file mode 100644 index 0000000000..cd107b13eb Binary files /dev/null and b/interface/resources/qml/LoginDialog/images/background.png differ diff --git a/interface/resources/qml/LoginDialog/images/background_tablet.jpg b/interface/resources/qml/LoginDialog/images/background_tablet.jpg deleted file mode 100644 index a46c052c04..0000000000 Binary files a/interface/resources/qml/LoginDialog/images/background_tablet.jpg and /dev/null differ diff --git a/interface/resources/qml/LoginDialog/images/background_tablet.png b/interface/resources/qml/LoginDialog/images/background_tablet.png new file mode 100644 index 0000000000..5c288590ab Binary files /dev/null and b/interface/resources/qml/LoginDialog/images/background_tablet.png differ diff --git a/interface/resources/qml/OverlayLoginDialog.qml b/interface/resources/qml/OverlayLoginDialog.qml index 3de3f68942..0ad2c57e5f 100644 --- a/interface/resources/qml/OverlayLoginDialog.qml +++ b/interface/resources/qml/OverlayLoginDialog.qml @@ -55,7 +55,8 @@ FocusScope { Image { z: -10 id: loginDialogBackground - source: "LoginDialog/images/background.jpg" + fillMode: Image.PreserveAspectCrop + source: "LoginDialog/images/background.png" anchors.fill: parent } @@ -149,6 +150,6 @@ FocusScope { Component.onCompleted: { keyboardTimer.start(); - bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); + bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false }); } } diff --git a/interface/resources/qml/androidAppForce/ForFolderListModel.qml b/interface/resources/qml/androidAppForce/ForFolderListModel.qml new file mode 100644 index 0000000000..3fba3f11fa --- /dev/null +++ b/interface/resources/qml/androidAppForce/ForFolderListModel.qml @@ -0,0 +1,26 @@ +import QtQuick 2.2 +import QtQuick.Dialogs 1.1 +import Qt.labs.folderlistmodel 2.11 + +Item { + width: 640 + height: 480 + + ListView { + width: 200; height: 400 + + FolderListModel { + id: folderModel + folder: "assets:/frames/" + nameFilters: ["*.json"] + } + + Component { + id: fileDelegate + Text { text: fileName } + } + + model: folderModel + delegate: fileDelegate + } +} diff --git a/interface/resources/qml/controlsUit/SpinBox.qml b/interface/resources/qml/controlsUit/SpinBox.qml index 34794d80c7..564157efb0 100644 --- a/interface/resources/qml/controlsUit/SpinBox.qml +++ b/interface/resources/qml/controlsUit/SpinBox.qml @@ -57,16 +57,23 @@ SpinBox { locale: Qt.locale("en_US") - onValueModified: realValue = value/factor - onValueChanged: realValue = value/factor + onValueModified: { + realValue = value / factor + } + + onValueChanged: { + realValue = value / factor + spinBox.editingFinished(); + } + onRealValueChanged: { - var newValue = Math.round(realValue*factor); + var newValue = Math.round(realValue * factor); if(value != newValue) { value = newValue; } } - stepSize: realStepSize*factor + stepSize: realStepSize * factor to : realTo*factor from : realFrom*factor @@ -90,11 +97,11 @@ SpinBox { } textFromValue: function(value, locale) { - return parseFloat(value/factor).toFixed(decimals); + return parseFloat(value / factor).toFixed(decimals); } valueFromText: function(text, locale) { - return Number.fromLocaleString(locale, text)*factor; + return Number.fromLocaleString(locale, text) * factor; } @@ -102,7 +109,7 @@ SpinBox { id: spinboxText z: 2 color: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.faintGray) : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight @@ -112,8 +119,6 @@ SpinBox { verticalAlignment: Qt.AlignVCenter leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding width: spinBox.width - hifi.dimensions.spinnerSize - onEditingFinished: spinBox.editingFinished() - Text { id: suffixText x: metrics.advanceWidth(spinboxText.text + '*') @@ -125,7 +130,7 @@ SpinBox { } color: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.faintGray) : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) text: suffix verticalAlignment: Qt.AlignVCenter @@ -170,6 +175,22 @@ SpinBox { } } + Keys.onPressed: { + if (event.key === Qt.Key_Return) { + if (!spinboxText.acceptableInput) { + var number = spinBox.valueFromText(spinboxText.text, spinBox.locale) / spinBox.factor + + if (number < spinBox.minimumValue) { + number = spinBox.minimumValue; + } else if (number > maximumValue) { + number = spinBox.maximumValue; + } + + spinboxText.text = spinBox.textFromValue(Math.round(number * factor), spinBox.locale) + } + } + } + HifiControls.Label { id: spinBoxLabel text: spinBox.label diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 7bffcbe75c..8d6444bc0e 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -14,6 +14,8 @@ import QtQuick 2.5 import controlsUit 1.0 as HifiControlsUit import stylesUit 1.0 as HifiStylesUit +import TabletScriptingInterface 1.0 + import "../LoginDialog" FocusScope { @@ -25,10 +27,9 @@ FocusScope { width: parent.width height: parent.height - signal sendToScript(var message); - signal canceled(); + property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); - property bool isHMD: false + property bool isHMD: HMD.active property bool gotoPreviousApp: false; property bool keyboardEnabled: false @@ -52,6 +53,7 @@ FocusScope { } function tryDestroy() { + tabletProxy.gotoHomeScreen(); } MouseArea { @@ -76,7 +78,7 @@ FocusScope { interval: 200 onTriggered: { - if (MenuInterface.isOptionChecked("Use 3D Keyboard")) { + if (MenuInterface.isOptionChecked("Use 3D Keyboard") && root.isHMD) { KeyboardScriptingInterface.raised = true; } } @@ -95,7 +97,8 @@ FocusScope { Image { z: -10 id: loginDialogBackground - source: "../LoginDialog/images/background_tablet.jpg" + fillMode: Image.PreserveAspectCrop + source: "../LoginDialog/images/background_tablet.png" anchors.fill: parent } @@ -168,11 +171,13 @@ FocusScope { Component.onDestruction: { loginKeyboard.raised = false; - KeyboardScriptingInterface.raised = false; + if (root.isHMD) { + KeyboardScriptingInterface.raised = false; + } } Component.onCompleted: { keyboardTimer.start(); - bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false }); + bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false }); } } diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 247a42428a..1abd4f45ff 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -148,7 +148,7 @@ Windows.ScrollingWindow { } function canAddToWorld(path) { - var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i]; if (selectedItemCount > 1) { return false; diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 171ea4fd15..753b9c5a81 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -254,6 +254,7 @@ Rectangle { onSaveClicked: function() { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', + hmdAvatarAlignmentType : settings.hmdAvatarAlignmentTypeIsEyes ? 'eyes' : 'head', collisionsEnabled : settings.environmentCollisionsOn, otherAvatarsCollisionsEnabled : settings.otherAvatarsCollisionsOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index b6d0167ba5..39c48646d3 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -37,6 +37,7 @@ Rectangle { property alias dominantHandIsLeft: leftHandRadioButton.checked property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledRadiobutton.checked property alias environmentCollisionsOn: environmentCollisionsEnabledRadiobutton.checked + property alias hmdAvatarAlignmentTypeIsEyes: eyesRadioButton.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -65,6 +66,11 @@ Rectangle { } else { environmentCollisionsDisabledRadiobutton.checked = true; } + if (settings.hmdAvatarAlignmentType === 'eyes') { + eyesRadioButton.checked = true; + } else { + headRadioButton.checked = true; + } avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; @@ -210,7 +216,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right - rows: 2 + rows: 4 rowSpacing: 25 columns: 3 @@ -233,7 +239,7 @@ Rectangle { Layout.row: 0 Layout.column: 1 - Layout.leftMargin: -20 + Layout.leftMargin: -15 ButtonGroup.group: leftRight checked: true @@ -249,7 +255,7 @@ Rectangle { id: rightHandRadioButton Layout.row: 0 - Layout.column: 3 + Layout.column: 2 Layout.rightMargin: -15 ButtonGroup.group: leftRight @@ -260,7 +266,7 @@ Rectangle { text: "Right" boxSize: 20 } - + HifiConstants { id: hifi } @@ -272,17 +278,17 @@ Rectangle { Layout.column: 0 text: "Avatar to avatar collision" } - + ButtonGroup { id: otherAvatarsOnOff } - + HifiControlsUit.RadioButton { id: otherAvatarsCollisionsEnabledRadiobutton Layout.row: 1 Layout.column: 1 - Layout.leftMargin: -20 + Layout.leftMargin: -15 ButtonGroup.group: otherAvatarsOnOff @@ -297,7 +303,7 @@ Rectangle { id: otherAvatarsCollisionsDisabledRadiobutton Layout.row: 1 - Layout.column: 3 + Layout.column: 2 Layout.rightMargin: -15 ButtonGroup.group: otherAvatarsOnOff @@ -320,13 +326,13 @@ Rectangle { ButtonGroup { id: worldOnOff } - + HifiControlsUit.RadioButton { id: environmentCollisionsEnabledRadiobutton Layout.row: 2 Layout.column: 1 - Layout.leftMargin: -20 + Layout.leftMargin: -15 ButtonGroup.group: worldOnOff @@ -341,7 +347,7 @@ Rectangle { id: environmentCollisionsDisabledRadiobutton Layout.row: 2 - Layout.column: 3 + Layout.column: 2 Layout.rightMargin: -15 ButtonGroup.group: worldOnOff @@ -352,6 +358,52 @@ Rectangle { text: "Off" boxSize: 20 } + + // TextStyle9 + RalewaySemiBold { + size: 17; + Layout.row: 3 + Layout.column: 0 + text: "HMD Alignment" + } + + ButtonGroup { + id: headEyes + } + + HifiControlsUit.RadioButton { + id: headRadioButton + + Layout.row: 3 + Layout.column: 1 + Layout.leftMargin: -15 + + ButtonGroup.group: headEyes + checked: true + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Head" + boxSize: 20 + } + + HifiControlsUit.RadioButton { + id: eyesRadioButton + + Layout.row: 3 + Layout.column: 2 + Layout.rightMargin: -15 + + ButtonGroup.group: headEyes + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Eyes" + boxSize: 20 + } + } ColumnLayout { diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index b5374b2fe0..62ec264fc9 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -148,7 +148,7 @@ Rectangle { } function canAddToWorld(path) { - var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i]; if (selectedItemCount > 1) { return false; diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index a2104826c3..a9d67fec35 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -70,7 +70,7 @@ Flickable { readonly property bool hmdDesktop: hmdInDesktop.checked property int state: buttonState.disabled - property var lastConfiguration: null + property var lastConfiguration: null HifiConstants { id: hifi } @@ -90,7 +90,6 @@ Flickable { anchors.fill: parent propagateComposedEvents: true onPressed: { - parent.forceActiveFocus() mouse.accepted = false; } } @@ -169,9 +168,7 @@ Flickable { boxRadius: 7 visible: viveInDesktop.checked - anchors.top: viveInDesktop.bottom anchors.topMargin: 5 - anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 onClicked: { @@ -214,13 +211,13 @@ Flickable { onRealValueChanged: { sendConfigurationSettings(); - openVrConfiguration.forceActiveFocus(); } } HifiControls.SpinBox { id: headZOffset + z: 10 width: 112 label: "Z Offset" minimumValue: -50 @@ -232,7 +229,6 @@ Flickable { onRealValueChanged: { sendConfigurationSettings(); - openVrConfiguration.forceActiveFocus(); } } } @@ -326,7 +322,6 @@ Flickable { onRealValueChanged: { sendConfigurationSettings(); - openVrConfiguration.forceActiveFocus(); } } @@ -344,7 +339,6 @@ Flickable { onRealValueChanged: { sendConfigurationSettings(); - openVrConfiguration.forceActiveFocus(); } } } @@ -578,7 +572,6 @@ Flickable { onRealValueChanged: { sendConfigurationSettings(); - openVrConfiguration.forceActiveFocus(); } } @@ -596,7 +589,6 @@ Flickable { onRealValueChanged: { sendConfigurationSettings(); - openVrConfiguration.forceActiveFocus(); } } } @@ -747,8 +739,8 @@ Flickable { } Component.onCompleted: { - InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); lastConfiguration = composeConfigurationSettings(); + InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); } Component.onDestruction: { @@ -777,7 +769,6 @@ Flickable { calibrationTimer.interval = realValue * 1000; openVrConfiguration.countDown = realValue; numberAnimation.duration = calibrationTimer.interval; - openVrConfiguration.forceActiveFocus(); } } @@ -1048,6 +1039,9 @@ Flickable { } function updateButtonState() { + if (lastConfiguration === null) { + lastConfiguration = composeConfigurationSettings(); + } var settings = composeConfigurationSettings(); var bodySetting = settings["bodyConfiguration"]; var headSetting = settings["headConfiguration"]; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4bea8cabfb..7aa4d0dd81 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -120,6 +120,7 @@ #include #include #include +#include #include #include #include @@ -780,7 +781,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { if (auto steamClient = pluginManager->getSteamClientPlugin()) { steamClient->init(); } - PROFILE_SET_THREAD_NAME("Main Thread"); #if defined(Q_OS_WIN) @@ -2387,7 +2387,6 @@ void Application::updateVerboseLogging() { bool enable = menu->isOptionChecked(MenuOption::VerboseLogging); QString rules = - "hifi.*.debug=%1\n" "hifi.*.info=%1\n" "hifi.audio-stream.debug=false\n" "hifi.audio-stream.info=false"; @@ -2706,6 +2705,7 @@ Application::~Application() { if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { steamClient->shutdown(); } + DependencyManager::destroy(); DependencyManager::destroy(); // must be destroyed before the FramebufferCache @@ -3512,8 +3512,10 @@ void Application::resizeGL() { auto renderConfig = _graphicsEngine.getRenderEngine()->getConfiguration(); assert(renderConfig); auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); - assert(mainView); - mainView->setProperty("resolutionScale", renderResolutionScale); + // mainView can be null if we're rendering in forward mode + if (mainView) { + mainView->setProperty("resolutionScale", renderResolutionScale); + } displayPlugin->setRenderResolutionScale(renderResolutionScale); } @@ -4039,6 +4041,19 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_G: + if (isShifted && isMeta && Menu::getInstance() && Menu::getInstance()->getMenu("Developer")->isVisible()) { + static const QString HIFI_FRAMES_FOLDER_VAR = "HIFI_FRAMES_FOLDER"; + static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR) + ? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR) + : "hifiFrames"; + static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}"; + QString fullPath = FileUtils::computeDocumentPath(FileUtils::replaceDateTimeTokens(GPU_FRAME_TEMPLATE)); + if (FileUtils::canCreateFile(fullPath)) { + getActiveDisplayPlugin()->captureFrame(fullPath.toStdString()); + } + } + break; case Qt::Key_X: if (isShifted && isMeta) { auto offscreenUi = getOffscreenUI(); @@ -4820,6 +4835,10 @@ void Application::idle() { steamClient->runCallbacks(); } + if (auto oculusPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlugin->handleOVREvents(); + } + float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND; _lastTimeUpdated.start(); @@ -5958,6 +5977,13 @@ void Application::update(float deltaTime) { auto userInputMapper = DependencyManager::get(); + controller::HmdAvatarAlignmentType hmdAvatarAlignmentType; + if (myAvatar->getHmdAvatarAlignmentType() == "eyes") { + hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Eyes; + } else { + hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Head; + } + controller::InputCalibrationData calibrationData = { myAvatar->getSensorToWorldMatrix(), createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()), @@ -5971,7 +5997,8 @@ void Application::update(float deltaTime) { myAvatar->getRightArmCalibrationMat(), myAvatar->getLeftArmCalibrationMat(), myAvatar->getRightHandCalibrationMat(), - myAvatar->getLeftHandCalibrationMat() + myAvatar->getLeftHandCalibrationMat(), + hmdAvatarAlignmentType }; InputPluginPointer keyboardMousePlugin; @@ -8131,7 +8158,18 @@ void Application::toggleLogDialog() { return; } if (! _logDialog) { + + bool keepOnTop =_keepLogWindowOnTop.get(); +#ifdef Q_OS_WIN + _logDialog = new LogDialog(keepOnTop ? qApp->getWindow() : nullptr, getLogger()); +#else _logDialog = new LogDialog(nullptr, getLogger()); + + if (keepOnTop) { + Qt::WindowFlags flags = _logDialog->windowFlags() | Qt::Tool; + _logDialog->setWindowFlags(flags); + } +#endif } if (_logDialog->isVisible()) { @@ -8141,6 +8179,19 @@ void Application::toggleLogDialog() { } } + void Application::recreateLogWindow(int keepOnTop) { + _keepLogWindowOnTop.set(keepOnTop != 0); + if (_logDialog) { + bool toggle = _logDialog->isVisible(); + _logDialog->close(); + _logDialog = nullptr; + + if (toggle) { + toggleLogDialog(); + } + } + } + void Application::toggleEntityScriptServerLogDialog() { if (! _entityScriptServerLogDialog) { _entityScriptServerLogDialog = new EntityScriptServerLogDialog(nullptr); diff --git a/interface/src/Application.h b/interface/src/Application.h index 55d396bb62..5ecf3c7d5f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -217,6 +217,8 @@ public: void setDesktopTabletScale(float desktopTabletScale); bool getDesktopTabletBecomesToolbarSetting() { return _desktopTabletBecomesToolbarSetting.get(); } + bool getLogWindowOnTopSetting() { return _keepLogWindowOnTop.get(); } + void setLogWindowOnTopSetting(bool keepOnTop) { _keepLogWindowOnTop.set(keepOnTop); } void setDesktopTabletBecomesToolbarSetting(bool value); bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } void setHmdTabletBecomesToolbarSetting(bool value); @@ -365,6 +367,7 @@ public slots: Q_INVOKABLE void loadDialog(); Q_INVOKABLE void loadScriptURLDialog() const; void toggleLogDialog(); + void recreateLogWindow(int); void toggleEntityScriptServerLogDialog(); Q_INVOKABLE void showAssetServerWidget(QString filePath = ""); Q_INVOKABLE void loadAddAvatarBookmarkDialog() const; @@ -653,6 +656,7 @@ private: Setting::Handle _constrainToolbarPosition; Setting::Handle _preferredCursor; Setting::Handle _miniTabletEnabledSetting; + Setting::Handle _keepLogWindowOnTop { "keepLogWindowOnTop", false }; float _scaleMirror; float _mirrorYawOffset; diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index dd99907b8e..5fb9c9a0ee 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -288,39 +288,37 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; _measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity; - if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) { - _measuredLinearVelocitiesIndex = 0; + _measuredLinearVelocitiesIndex %= AvatarActionHold::velocitySmoothFrames; + } + + if (_kinematicSetVelocity) { + glm::vec3 measuredLinearVelocity = _measuredLinearVelocities[0]; + for (int i = 1; i < AvatarActionHold::velocitySmoothFrames; i++) { + // there is a bit of lag between when someone releases the trigger and when the software reacts to + // the release. we calculate the velocity from previous frames but we don't include several + // of the most recent. + // + // if _measuredLinearVelocitiesIndex is + // 0 -- ignore i of 3 4 5 + // 1 -- ignore i of 4 5 0 + // 2 -- ignore i of 5 0 1 + // 3 -- ignore i of 0 1 2 + // 4 -- ignore i of 1 2 3 + // 5 -- ignore i of 2 3 4 + + // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back. + // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { + // continue; + // } + + measuredLinearVelocity += _measuredLinearVelocities[i]; } - } - - glm::vec3 measuredLinearVelocity; - for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) { - // there is a bit of lag between when someone releases the trigger and when the software reacts to - // the release. we calculate the velocity from previous frames but we don't include several - // of the most recent. - // - // if _measuredLinearVelocitiesIndex is - // 0 -- ignore i of 3 4 5 - // 1 -- ignore i of 4 5 0 - // 2 -- ignore i of 5 0 1 - // 3 -- ignore i of 0 1 2 - // 4 -- ignore i of 1 2 3 - // 5 -- ignore i of 2 3 4 - - // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back. - // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || - // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || - // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { - // continue; - // } - - measuredLinearVelocity += _measuredLinearVelocities[i]; - } - measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames + measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames // - 3 // 3 because of the 3 we skipped, above ); - if (_kinematicSetVelocity) { rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity)); rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget)); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp old mode 100644 new mode 100755 index b9c7dc729d..188d7d70a7 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -270,7 +270,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); - avatar->updateCollisionGroup(_myAvatar->getOtherAvatarsCollisionsEnabled()); if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } @@ -376,7 +375,6 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { } AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { - auto otherAvatar = new OtherAvatar(qApp->thread()); otherAvatar->setSessionUUID(sessionUUID); auto nodeList = DependencyManager::get(); @@ -452,6 +450,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar _spaceProxiesToDelete.push_back(avatar->getSpaceIndex()); } AvatarHashMap::handleRemovedAvatar(avatar, removalReason); + avatar->tearDownGrabs(); avatar->die(); queuePhysicsChange(avatar); diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp old mode 100644 new mode 100755 index 3fa59ea967..77fc81fa04 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -15,7 +15,6 @@ #include #include - AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; @@ -172,7 +171,10 @@ QUuid AvatarMotionState::getSimulatorID() const { // virtual void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const { group = _collisionGroup; - mask = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0 : Physics::getDefaultCollisionMask(group); + mask = Physics::getDefaultCollisionMask(group); + if (!_avatar->getCollideWithOtherAvatars()) { + mask &= ~(BULLET_COLLISION_GROUP_MY_AVATAR | BULLET_COLLISION_GROUP_OTHER_AVATAR); + } } // virtual diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 84d6689e92..c0c259c0bc 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -95,6 +95,37 @@ const float CENTIMETERS_PER_METER = 100.0f; const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" }; +static const QString USER_RECENTER_MODEL_FORCE_SIT = QStringLiteral("ForceSit"); +static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStand"); +static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto"); +static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean"); + +MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) { + if (str == USER_RECENTER_MODEL_FORCE_SIT) { + return MyAvatar::ForceSit; + } else if (str == USER_RECENTER_MODEL_FORCE_STAND) { + return MyAvatar::ForceStand; + } else if (str == USER_RECENTER_MODEL_DISABLE_HMD_LEAN) { + return MyAvatar::DisableHMDLean; + } else { + return MyAvatar::Auto; + } +} + +QString userRecenterModelToString(MyAvatar::SitStandModelType model) { + switch (model) { + case MyAvatar::ForceSit: + return USER_RECENTER_MODEL_FORCE_SIT; + case MyAvatar::ForceStand: + return USER_RECENTER_MODEL_FORCE_STAND; + case MyAvatar::DisableHMDLean: + return USER_RECENTER_MODEL_DISABLE_HMD_LEAN; + case MyAvatar::Auto: + default: + return USER_RECENTER_MODEL_AUTO; + } +} + MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), _yawSpeed(YAW_SPEED_DEFAULT), @@ -125,6 +156,7 @@ MyAvatar::MyAvatar(QThread* thread) : _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND), + _hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE), _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f), _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale), _yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed), @@ -138,7 +170,8 @@ MyAvatar::MyAvatar(QThread* thread) : _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn), _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT), _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD), - _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0) + _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0), + _userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO) { _clientTraitsHandler.reset(new ClientTraitsHandler(this)); @@ -205,12 +238,12 @@ MyAvatar::MyAvatar(QThread* thread) : if (recordingInterface->getPlayFromCurrentLocation()) { setRecordingBasis(); } - _previousCollisionGroup = _characterController.computeCollisionGroup(); + _previousCollisionMask = _characterController.computeCollisionMask(); _characterController.setCollisionless(true); } else { clearRecordingBasis(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) { + if (_previousCollisionMask != BULLET_COLLISION_MASK_COLLISIONLESS) { _characterController.setCollisionless(false); } } @@ -286,10 +319,25 @@ MyAvatar::~MyAvatar() { _myScriptEngine = nullptr; } +QString MyAvatar::getDominantHand() const { + return _dominantHand.get(); +} + void MyAvatar::setDominantHand(const QString& hand) { if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) { - _dominantHand = hand; - emit dominantHandChanged(_dominantHand); + _dominantHand.set(hand); + emit dominantHandChanged(hand); + } +} + +QString MyAvatar::getHmdAvatarAlignmentType() const { + return _hmdAvatarAlignmentType.get(); +} + +void MyAvatar::setHmdAvatarAlignmentType(const QString& type) { + if (type != _hmdAvatarAlignmentType.get()) { + _hmdAvatarAlignmentType.set(type); + emit hmdAvatarAlignmentTypeChanged(type); } } @@ -377,6 +425,7 @@ void MyAvatar::resetSensorsAndBody() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "resetSensorsAndBody"); return; + } qApp->getActiveDisplayPlugin()->resetSensors(); @@ -817,7 +866,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { // and all of its joints, now update our attachements. Avatar::simulateAttachments(deltaTime); relayJointDataToChildren(); - if (updateGrabs()) { + if (applyGrabChanges()) { _cauterizationNeedsUpdate = true; } @@ -1277,7 +1326,8 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { } void MyAvatar::saveData() { - _dominantHandSetting.set(_dominantHand); + _dominantHandSetting.set(getDominantHand()); + _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); _scaleSetting.set(_targetScale); _yawSpeedSetting.set(_yawSpeed); @@ -1300,6 +1350,7 @@ void MyAvatar::saveData() { _useSnapTurnSetting.set(_useSnapTurn); _userHeightSetting.set(getUserHeight()); _flyingHMDSetting.set(getFlyingHMDPref()); + _userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel())); auto hmdInterface = DependencyManager::get(); saveAvatarEntityDataToSettings(); @@ -1882,9 +1933,12 @@ void MyAvatar::loadData() { setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString()); setSnapTurn(_useSnapTurnSetting.get()); setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower()); + setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower()); setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setTargetScale(_scaleSetting.get()); + setUserRecenterModel(stringToUserRecenterModel(_userRecenterModelSetting.get(USER_RECENTER_MODEL_AUTO))); + setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); setEnableDebugDrawBaseOfSupport(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBaseOfSupport)); @@ -2534,7 +2588,7 @@ void MyAvatar::updateMotors() { float verticalMotorTimescale; if (_characterController.getState() == CharacterController::State::Hover || - _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { @@ -2544,7 +2598,7 @@ void MyAvatar::updateMotors() { if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_characterController.getState() == CharacterController::State::Hover || - _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); } else { // non-hovering = walking: follow camera twist about vertical but not lift @@ -2599,7 +2653,7 @@ void MyAvatar::prepareForPhysicsSimulation() { qDebug() << "Warning: getParentVelocity failed" << getID(); parentVelocity = glm::vec3(); } - _characterController.handleChangedCollisionGroup(); + _characterController.handleChangedCollisionMask(); _characterController.setParentVelocity(parentVelocity); _characterController.setScaleFactor(getSensorToWorldScale()); @@ -3279,7 +3333,7 @@ void MyAvatar::updateOrientation(float deltaTime) { head->setBaseRoll(ROLL(euler)); } else { head->setBaseYaw(0.0f); - head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime + head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime + getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT); head->setBaseRoll(0.0f); } @@ -3325,7 +3379,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { glm::vec3 direction = forward + right; if (state == CharacterController::State::Hover || - _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; direction += up; } @@ -3881,7 +3935,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) { bool MyAvatar::getCollisionsEnabled() { // may return 'false' even though the collisionless option was requested // because the zone may disallow collisionless avatars - return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; + return _characterController.computeCollisionMask() != BULLET_COLLISION_MASK_COLLISIONLESS; } void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) { @@ -3890,7 +3944,11 @@ void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) { QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled)); return; } + bool change = _collideWithOtherAvatars != enabled; _collideWithOtherAvatars = enabled; + if (change) { + setCollisionWithOtherAvatarsFlags(); + } emit otherAvatarsCollisionsEnabledChanged(enabled); } @@ -3898,6 +3956,11 @@ bool MyAvatar::getOtherAvatarsCollisionsEnabled() { return _collideWithOtherAvatars; } +void MyAvatar::setCollisionWithOtherAvatarsFlags() { + _characterController.setCollideWithOtherAvatars(_collideWithOtherAvatars); + _characterController.setPendingFlagsUpdateCollisionMask(); +} + void MyAvatar::updateCollisionCapsuleCache() { glm::vec3 start, end; float radius; @@ -4752,7 +4815,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons } bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { - const float CYLINDER_TOP = 0.1f; + const float CYLINDER_TOP = 2.0f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; @@ -5310,7 +5373,7 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabData.remove(grabID)) { - _deletedAvatarGrabs.insert(grabID); + _grabsToDelete.push_back(grabID); tellHandler = true; } }); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h old mode 100644 new mode 100755 index 3ebde748b1..089ff5dc07 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -252,6 +252,7 @@ class MyAvatar : public Avatar { const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; + const QString DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE = "head"; using Clock = std::chrono::system_clock; using TimePoint = Clock::time_point; @@ -297,6 +298,8 @@ public: void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); + void setCollisionWithOtherAvatarsFlags() override; + /**jsdoc * @function MyAvatar.resetSensorsAndBody */ @@ -517,7 +520,18 @@ public: * @function MyAvatar.getDominantHand * @returns {string} */ - Q_INVOKABLE QString getDominantHand() const { return _dominantHand; } + Q_INVOKABLE QString getDominantHand() const; + + /**jsdoc + * @function MyAvatar.setHmdAvatarAlignmentType + * @param {string} hand + */ + Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& hand); + /**jsdoc + * @function MyAvatar.setHmdAvatarAlignmentType + * @returns {string} + */ + Q_INVOKABLE QString getHmdAvatarAlignmentType() const; /**jsdoc * @function MyAvatar.setCenterOfGravityModelEnabled @@ -1583,6 +1597,13 @@ signals: */ void dominantHandChanged(const QString& hand); + /**jsdoc + * @function MyAvatar.hmdAvatarAlignmentTypeChanged + * @param {string} type + * @returns {Signal} + */ + void hmdAvatarAlignmentTypeChanged(const QString& type); + /**jsdoc * @function MyAvatar.sensorToWorldScaleChanged * @param {number} scale @@ -1732,7 +1753,7 @@ private: SharedSoundPointer _collisionSound; MyCharacterController _characterController; - int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR }; + int32_t _previousCollisionMask { BULLET_COLLISION_MASK_MY_AVATAR }; AvatarWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; @@ -1771,7 +1792,8 @@ private: ThreadSafeValueCache _prefOverrideAnimGraphUrl; QUrl _fstAnimGraphOverrideUrl; bool _useSnapTurn { true }; - QString _dominantHand { DOMINANT_RIGHT_HAND }; + ThreadSafeValueCache _dominantHand { DOMINANT_RIGHT_HAND }; + ThreadSafeValueCache _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE }; const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec @@ -1944,6 +1966,7 @@ private: TimePoint _nextTraitsSendWindow; Setting::Handle _dominantHandSetting; + Setting::Handle _hmdAvatarAlignmentTypeSetting; Setting::Handle _headPitchSetting; Setting::Handle _scaleSetting; Setting::Handle _yawSpeedSetting; @@ -1960,6 +1983,7 @@ private: Setting::Handle _allowTeleportingSetting { "allowTeleporting", true }; std::vector> _avatarEntityIDSettings; std::vector> _avatarEntityDataSettings; + Setting::Handle _userRecenterModelSetting; // AvatarEntities stuff: // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 798dbc91ed..821b01c2c6 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -202,6 +202,29 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm: return result.hitFraction < 1.0f; } +int32_t MyCharacterController::computeCollisionMask() const { + int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR; + if (_collisionless && _collisionlessAllowed) { + collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS; + } else if (!_collideWithOtherAvatars) { + collisionMask &= ~BULLET_COLLISION_GROUP_OTHER_AVATAR; + } + return collisionMask; +} + +void MyCharacterController::handleChangedCollisionMask() { + if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) { + // ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody + if (_dynamicsWorld) { + _dynamicsWorld->removeRigidBody(_rigidBody); + int32_t collisionMask = computeCollisionMask(); + _dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask); + } + _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK; + updateCurrentGravity(); + } +} + btConvexHullShape* MyCharacterController::computeShape() const { // HACK: the avatar collides using convex hull with a collision margin equal to // the old capsule radius. Two points define a capsule and additional points are diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h old mode 100644 new mode 100755 index fd9caface2..76fe588e71 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -42,6 +42,12 @@ public: void setDensity(btScalar density) { _density = density; } + int32_t computeCollisionMask() const override; + void handleChangedCollisionMask() override; + + bool _collideWithOtherAvatars{ true }; + void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; } + protected: void initRayShotgun(const btCollisionWorld* world); void updateMassProperties() override; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp old mode 100644 new mode 100755 index 356b365f93..26d69841d0 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -187,7 +187,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } - bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); + bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS); if (isFlying != _prevIsFlying) { const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f; _flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME; @@ -198,7 +198,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // if hips are not under direct control, estimate the hips position. if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { - bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); + bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS); // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp old mode 100644 new mode 100755 index 70b1d556be..464dfcb4d8 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -138,17 +138,9 @@ void OtherAvatar::rebuildCollisionShape() { } } -void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) { +void OtherAvatar::setCollisionWithOtherAvatarsFlags() { if (_motionState) { - bool collides = _motionState->getCollisionGroup() == BULLET_COLLISION_GROUP_OTHER_AVATAR && myAvatarCollide; - if (_collideWithOtherAvatars != collides) { - if (!myAvatarCollide) { - _collideWithOtherAvatars = false; - } - auto newCollisionGroup = _collideWithOtherAvatars ? BULLET_COLLISION_GROUP_OTHER_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; - _motionState->setCollisionGroup(newCollisionGroup); - _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - } + _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); } } @@ -228,7 +220,7 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { { PROFILE_RANGE(simulation, "grabs"); - updateGrabs(); + applyGrabChanges(); } updateFadingStatus(); diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h old mode 100644 new mode 100755 index bf22da4d5e..4bbe75103d --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -44,7 +44,9 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; - void updateCollisionGroup(bool myAvatarCollide); + bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; } + + void setCollisionWithOtherAvatarsFlags() override; void simulate(float deltaTime, bool inView) override; diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp index e27b622262..e648dc222a 100644 --- a/interface/src/ui/BaseLogDialog.cpp +++ b/interface/src/ui/BaseLogDialog.cpp @@ -20,9 +20,9 @@ #include const int TOP_BAR_HEIGHT = 124; -const int INITIAL_WIDTH = 720; +const int INITIAL_WIDTH = 800; const int INITIAL_HEIGHT = 480; -const int MINIMAL_WIDTH = 700; +const int MINIMAL_WIDTH = 780; const int SEARCH_BUTTON_LEFT = 25; const int SEARCH_BUTTON_WIDTH = 20; const int SEARCH_TOGGLE_BUTTON_WIDTH = 50; diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index 26a5a24de8..1eaad05e33 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -19,6 +19,9 @@ #include +#include "Application.h" +#include "MainWindow.h" + const int REVEAL_BUTTON_WIDTH = 122; const int ALL_LOGS_BUTTON_WIDTH = 90; const int MARGIN_LEFT = 25; @@ -148,6 +151,16 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _messageCount->setObjectName("messageCount"); _messageCount->show(); + _keepOnTopBox = new QCheckBox(" Keep window on top", this); + bool isOnTop = qApp-> getLogWindowOnTopSetting(); + _keepOnTopBox->setCheckState(isOnTop ? Qt::Checked : Qt::Unchecked); +#ifdef Q_OS_WIN + connect(_keepOnTopBox, &QCheckBox::stateChanged, qApp, &Application::recreateLogWindow); +#else + connect(_keepOnTopBox, &QCheckBox::stateChanged, this, &LogDialog::handleKeepWindowOnTop); +#endif + _keepOnTopBox->show(); + _extraDebuggingBox = new QCheckBox("Extra debugging", this); if (_logger->extraDebugging()) { _extraDebuggingBox->setCheckState(Qt::Checked); @@ -183,6 +196,11 @@ void LogDialog::resizeEvent(QResizeEvent* event) { THIRD_ROW, COMBOBOX_WIDTH, ELEMENT_HEIGHT); + + _keepOnTopBox->setGeometry(width() - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN - ALL_LOGS_BUTTON_WIDTH - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN, + THIRD_ROW, + COMBOBOX_WIDTH, + ELEMENT_HEIGHT); _messageCount->setGeometry(_leftPad, THIRD_ROW, COMBOBOX_WIDTH, @@ -234,6 +252,23 @@ void LogDialog::handleInfoPrintBox(int state) { printLogFile(); } +void LogDialog::handleKeepWindowOnTop(int state) { + bool keepOnTop = (state != 0); + + Qt::WindowFlags flags = windowFlags(); + + if (keepOnTop) { + flags |= Qt::Tool; + } else { + flags &= ~Qt::Tool; + } + + setWindowFlags(flags); + qApp->setLogWindowOnTopSetting(keepOnTop); + + show(); +} + void LogDialog::handleCriticalPrintBox(int state) { _logger->setCriticalPrint(state != 0); printLogFile(); diff --git a/interface/src/ui/LogDialog.h b/interface/src/ui/LogDialog.h index eb92d4b381..d3ee81ca7e 100644 --- a/interface/src/ui/LogDialog.h +++ b/interface/src/ui/LogDialog.h @@ -34,6 +34,7 @@ public slots: private slots: void handleRevealButton(); void handleExtraDebuggingCheckbox(int); + void handleKeepWindowOnTop(int); void handleDebugPrintBox(int); void handleInfoPrintBox(int); void handleCriticalPrintBox(int); @@ -55,6 +56,7 @@ protected: private: QCheckBox* _extraDebuggingBox; + QCheckBox* _keepOnTopBox; QPushButton* _revealLogButton; QPushButton* _allLogsButton; QCheckBox* _debugPrintBox; diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 1b170e7a32..b4f504822f 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -109,8 +110,16 @@ bool LoginDialog::isSteamRunning() const { return steamClient && steamClient->isRunning(); } -bool LoginDialog::isOculusStoreRunning() const { - return qApp->property(hifi::properties::OCULUS_STORE).toBool(); +bool LoginDialog::isOculusRunning() const { + auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin(); + return (oculusPlatformPlugin && oculusPlatformPlugin->isRunning()); +} + +QString LoginDialog::oculusUserID() const { + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + return oculusPlatformPlugin->getOculusUserID(); + } + return ""; } void LoginDialog::dismissLoginDialog() { @@ -126,6 +135,79 @@ void LoginDialog::login(const QString& username, const QString& password) const DependencyManager::get()->requestAccessToken(username, password); } +void LoginDialog::loginThroughOculus() { + qDebug() << "Attempting to login through Oculus"; + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) { + DependencyManager::get()->requestAccessTokenWithOculus(nonce, oculusID); + }); + } +} + +void LoginDialog::linkOculus() { + qDebug() << "Attempting to link Oculus account"; + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) { + if (nonce.isEmpty() || oculusID.isEmpty()) { + emit handleLoginFailed(); + return; + } + + JSONCallbackParameters callbackParams; + callbackParams.callbackReceiver = this; + callbackParams.jsonCallbackMethod = "linkCompleted"; + callbackParams.errorCallbackMethod = "linkFailed"; + const QString LINK_OCULUS_PATH = "api/v1/user/oculus/link"; + + QJsonObject payload; + payload["oculus_nonce"] = nonce; + payload["oculus_id"] = oculusID; + + auto accountManager = DependencyManager::get(); + accountManager->sendRequest(LINK_OCULUS_PATH, AccountManagerAuth::Required, + QNetworkAccessManager::PostOperation, callbackParams, + QJsonDocument(payload).toJson()); + }); + } +} + +void LoginDialog::createAccountFromOculus(QString email, QString username, QString password) { + qDebug() << "Attempting to create account from Oculus info"; + if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { + oculusPlatformPlugin->requestNonceAndUserID([this, email, username, password] (QString nonce, QString oculusID) { + if (nonce.isEmpty() || oculusID.isEmpty()) { + emit handleLoginFailed(); + return; + } + + JSONCallbackParameters callbackParams; + callbackParams.callbackReceiver = this; + callbackParams.jsonCallbackMethod = "createCompleted"; + callbackParams.errorCallbackMethod = "createFailed"; + + const QString CREATE_ACCOUNT_FROM_OCULUS_PATH = "api/v1/user/oculus/create"; + + QJsonObject payload; + payload["oculus_nonce"] = nonce; + payload["oculus_id"] = oculusID; + if (!email.isEmpty()) { + payload["email"] = email; + } + if (!username.isEmpty()) { + payload["username"] = username; + } + if (!password.isEmpty()) { + payload["password"] = password; + } + + auto accountManager = DependencyManager::get(); + accountManager->sendRequest(CREATE_ACCOUNT_FROM_OCULUS_PATH, AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParams, + QJsonDocument(payload).toJson()); + }); + } +} + void LoginDialog::loginThroughSteam() { qDebug() << "Attempting to login through Steam"; if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { @@ -157,7 +239,7 @@ void LoginDialog::linkSteam() { const QString LINK_STEAM_PATH = "api/v1/user/steam/link"; QJsonObject payload; - payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket))); + payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket)); auto accountManager = DependencyManager::get(); accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required, @@ -184,9 +266,9 @@ void LoginDialog::createAccountFromSteam(QString username) { const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create"; QJsonObject payload; - payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket))); + payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket)); if (!username.isEmpty()) { - payload.insert("username", QJsonValue::fromVariant(QVariant(username))); + payload["username"] = username; } auto accountManager = DependencyManager::get(); @@ -214,6 +296,45 @@ void LoginDialog::createCompleted(QNetworkReply* reply) { } void LoginDialog::createFailed(QNetworkReply* reply) { + if (isOculusRunning()) { + auto replyData = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(replyData, &parseError); + if (parseError.error != QJsonParseError::NoError) { + emit handleCreateFailed(reply->errorString()); + return; + } + auto data = doc["data"]; + auto error = data["error"]; + auto oculusError = data["oculus"]; + auto user = error["username"].toArray(); + auto uid = error["uid"].toArray(); + auto email = error["email"].toArray(); + auto password = error["password"].toArray(); + QString reply; + if (uid[0].isString()) { + emit handleCreateFailed("Oculus ID " + uid[0].toString() + "."); + return; + } + if (user[0].isString()) { + reply = "Username " + user[0].toString() + "."; + } + if (email[0].isString()) { + reply.append((!reply.isEmpty()) ? "\n" : ""); + reply.append("Email " + email[0].toString() + "."); + } + if (password[0].isString()) { + reply.append((!reply.isEmpty()) ? "\n" : ""); + reply.append("Password " + password[0].toString() + "."); + } + if (!oculusError.isNull() && !oculusError.isUndefined()) { + emit handleCreateFailed("Could not verify token with Oculus. Please try again."); + return; + } else { + emit handleCreateFailed(reply); + return; + } + } emit handleCreateFailed(reply->errorString()); } diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 66773b638b..e2fa8adc61 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -22,7 +22,6 @@ extern const QUrl LOGIN_DIALOG; class LoginDialog : public OffscreenQmlDialog { Q_OBJECT - Q_PROPERTY(bool isLogIn READ getIsLogIn WRITE setIsLogIn) HIFI_QML_DECL public: @@ -67,24 +66,23 @@ protected slots: Q_INVOKABLE void dismissLoginDialog(); Q_INVOKABLE bool isSteamRunning() const; - Q_INVOKABLE bool isOculusStoreRunning() const; + Q_INVOKABLE bool isOculusRunning() const; + + Q_INVOKABLE QString oculusUserID() const; Q_INVOKABLE void login(const QString& username, const QString& password) const; Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void linkSteam(); Q_INVOKABLE void createAccountFromSteam(QString username = QString()); + Q_INVOKABLE void loginThroughOculus(); + Q_INVOKABLE void linkOculus(); + Q_INVOKABLE void createAccountFromOculus(QString email = QString(), QString username = QString(), QString password = QString()); Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password); Q_INVOKABLE void openUrl(const QString& url) const; Q_INVOKABLE bool getLoginDialogPoppedUp() const; - -private: - bool getIsLogIn() const { return _isLogIn; } - void setIsLogIn(const bool isLogIn) { _isLogIn = isLogIn; } - - bool _isLogIn{ false }; }; #endif // hifi_LoginDialog_h diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 16c2c1cc7e..cc48308f17 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -237,8 +237,17 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, _relativeDefaultPoses = _absoluteDefaultPoses; convertAbsolutePosesToRelative(_relativeDefaultPoses); + // build _jointIndicesByName hash for (int i = 0; i < _jointsSize; i++) { - _jointIndicesByName[_joints[i].name] = i; + auto iter = _jointIndicesByName.find(_joints[i].name); + if (iter != _jointIndicesByName.end()) { + // prefer joints over meshes if there is a name collision. + if (_joints[i].isSkeletonJoint && !_joints[iter.value()].isSkeletonJoint) { + iter.value() = i; + } + } else { + _jointIndicesByName.insert(_joints[i].name, i); + } } // build mirror map. diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 17d10cdf49..87d189696a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -324,88 +324,79 @@ void Avatar::removeAvatarEntitiesFromTree() { } } -bool Avatar::updateGrabs() { +bool Avatar::applyGrabChanges() { + if (!_avatarGrabDataChanged && _grabsToChange.empty() && _grabsToDelete.empty()) { + // early exit for most common case: nothing to do + return false; + } + bool grabAddedOrRemoved = false; - // update the Grabs according to any changes in _avatarGrabData _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabDataChanged) { + // collect changes in _avatarGrabData foreach (auto grabID, _avatarGrabData.keys()) { - AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID); - if (grabItr == _avatarGrabs.end()) { + MapOfGrabs::iterator itr = _avatarGrabs.find(grabID); + if (itr == _avatarGrabs.end()) { GrabPointer grab = std::make_shared(); grab->fromByteArray(_avatarGrabData.value(grabID)); _avatarGrabs[grabID] = grab; - _changedAvatarGrabs.insert(grabID); + _grabsToChange.insert(grabID); } else { - GrabPointer grab = grabItr.value(); - bool changed = grab->fromByteArray(_avatarGrabData.value(grabID)); + bool changed = itr->second->fromByteArray(_avatarGrabData.value(grabID)); if (changed) { - _changedAvatarGrabs.insert(grabID); + _grabsToChange.insert(grabID); } } } _avatarGrabDataChanged = false; } - auto treeRenderer = DependencyManager::get(); - auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr; - auto sessionID = DependencyManager::get()->getSessionUUID(); - - QMutableSetIterator delItr(_deletedAvatarGrabs); - while (delItr.hasNext()) { - QUuid grabID = delItr.next(); - GrabPointer grab = _avatarGrabs[grabID]; - if (!grab) { - delItr.remove(); + // delete _avatarGrabs + VectorOfIDs undeleted; + for (const auto& id : _grabsToDelete) { + MapOfGrabs::iterator itr = _avatarGrabs.find(id); + if (itr == _avatarGrabs.end()) { continue; } bool success; + const GrabPointer& grab = itr->second; SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); - - // only clear this entry from the _deletedAvatarGrabs if we found the entity. if (success && target) { - bool iShouldTellServer = target->getEditSenderID() == sessionID; - - EntityItemPointer entity = std::dynamic_pointer_cast(target); - if (entity && entity->isAvatarEntity() && (entity->getOwningAvatarID() == sessionID || - entity->getOwningAvatarID() == AVATAR_SELF_ID)) { - // this is our own avatar-entity, so we always tell the server about the release - iShouldTellServer = true; - } - target->removeGrab(grab); - delItr.remove(); - // in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server - // about the final position. - if (entityTree) { - bool force = true; - entityTree->withWriteLock([&] { - entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer); - }); - } + _avatarGrabs.erase(itr); grabAddedOrRemoved = true; + } else { + undeleted.push_back(id); } - _avatarGrabs.remove(grabID); - _changedAvatarGrabs.remove(grabID); } + _grabsToDelete = std::move(undeleted); - QMutableSetIterator changeItr(_changedAvatarGrabs); - while (changeItr.hasNext()) { - QUuid grabID = changeItr.next(); - GrabPointer& grab = _avatarGrabs[grabID]; + // change _avatarGrabs and add Actions to target + SetOfIDs unchanged; + for (const auto& id : _grabsToChange) { + MapOfGrabs::iterator itr = _avatarGrabs.find(id); + if (itr == _avatarGrabs.end()) { + continue; + } bool success; + const GrabPointer& grab = itr->second; SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); - if (success && target) { target->addGrab(grab); - // only clear this entry from the _changedAvatarGrabs if we found the entity. - changeItr.remove(); + if (isMyAvatar()) { + EntityItemPointer entity = std::dynamic_pointer_cast(target); + if (entity) { + entity->upgradeScriptSimulationPriority(PERSONAL_SIMULATION_PRIORITY); + } + } grabAddedOrRemoved = true; + } else { + unchanged.insert(id); } } + _grabsToChange = std::move(unchanged); }); return grabAddedOrRemoved; } @@ -413,8 +404,8 @@ bool Avatar::updateGrabs() { void Avatar::accumulateGrabPositions(std::map& grabAccumulators) { // relay avatar's joint position to grabbed target in a way that allows for averaging _avatarGrabsLock.withReadLock([&] { - foreach (auto grabID, _avatarGrabs.keys()) { - const GrabPointer& grab = _avatarGrabs.value(grabID); + for (const auto& entry : _avatarGrabs) { + const GrabPointer& grab = entry.second; if (!grab || !grab->getActionID().isNull()) { continue; // the accumulated value isn't used, in this case. @@ -432,6 +423,20 @@ void Avatar::accumulateGrabPositions(std::map& g }); } +void Avatar::tearDownGrabs() { + _avatarGrabsLock.withWriteLock([&] { + for (const auto& entry : _avatarGrabs) { + _grabsToDelete.push_back(entry.first); + } + _grabsToChange.clear(); + }); + applyGrabChanges(); + if (!_grabsToDelete.empty()) { + // some grabs failed to delete, which is a possible "leak", so we log about it + qWarning() << "Failed to tearDown" << _grabsToDelete.size() << "grabs for Avatar" << getID(); + } +} + void Avatar::relayJointDataToChildren() { forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -1929,3 +1934,12 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() { } return result; } + +void Avatar::clearAvatarGrabData(const QUuid& id) { + AvatarData::clearAvatarGrabData(id); + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabs.find(id) != _avatarGrabs.end()) { + _grabsToDelete.push_back(id); + } + }); +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index d5431ad2d2..1c3768e937 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include @@ -23,10 +26,12 @@ #include #include +#include +#include + #include "Head.h" #include "SkeletonModel.h" #include "Rig.h" -#include #include "MetaModelPayload.h" @@ -436,6 +441,8 @@ public: void accumulateGrabPositions(std::map& grabAccumulators); + void tearDownGrabs(); + signals: void targetScaleChanged(float targetScale); @@ -538,7 +545,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; - bool updateGrabs(); + bool applyGrabChanges(); void relayJointDataToChildren(); void fade(render::Transaction& transaction, render::Transition::Type type); @@ -625,8 +632,15 @@ protected: static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs); + void clearAvatarGrabData(const QUuid& grabID) override; - AvatarGrabMap _avatarGrabs; + using SetOfIDs = std::set; + using VectorOfIDs = std::vector; + using MapOfGrabs = std::map; + + MapOfGrabs _avatarGrabs; + SetOfIDs _grabsToChange; // updated grab IDs -- changes needed to entities or physics + VectorOfIDs _grabsToDelete; // deleted grab IDs -- changes needed to entities or physics }; #endif // hifi_Avatar_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp old mode 100644 new mode 100755 index ba3845e8e7..29f0d2b33d --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -639,7 +639,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // compute maxTranslationDimension before we send any joint data. float maxTranslationDimension = 0.001f; - for (int i = sendStatus.rotationsSent; i < numJoints; ++i) { + for (int i = sendStatus.translationsSent; i < numJoints; ++i) { const JointData& data = jointData[i]; if (!data.translationIsDefaultPose) { maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); @@ -1172,6 +1172,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); + if (collideWithOtherAvatarsChanged) { + setCollisionWithOtherAvatarsFlags(); + } if (somethingChanged) { _additionalFlagsChanged = now; } @@ -2428,7 +2431,8 @@ static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); enum class JsonAvatarFrameVersion : int { JointRotationsInRelativeFrame = 0, JointRotationsInAbsoluteFrame, - JointDefaultPoseBits + JointDefaultPoseBits, + JointUnscaledTranslations, }; QJsonValue toJsonValue(const JointData& joint) { @@ -2445,7 +2449,16 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) { if (json.isArray()) { QJsonArray array = json.toArray(); result.rotation = quatFromJsonValue(array[0]); + result.translation = vec3FromJsonValue(array[1]); + + // In old recordings, translations are scaled by _geometryOffset. Undo that scaling. + if (version < (int)JsonAvatarFrameVersion::JointUnscaledTranslations) { + // because we don't have access to the actual _geometryOffset used. we have to guess. + // most avatar FBX files were authored in centimeters. + const float METERS_TO_CENTIMETERS = 100.0f; + result.translation *= METERS_TO_CENTIMETERS; + } if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) { result.rotationIsDefaultPose = array[2].toBool(); result.translationIsDefaultPose = array[3].toBool(); @@ -2464,7 +2477,7 @@ void AvatarData::avatarEntityDataToJson(QJsonObject& root) const { QJsonObject AvatarData::toJson() const { QJsonObject root; - root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits; + root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointUnscaledTranslations; if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); @@ -2997,7 +3010,6 @@ void AvatarData::clearAvatarGrabData(const QUuid& grabID) { _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabData.remove(grabID)) { _avatarGrabDataChanged = true; - _deletedAvatarGrabs.insert(grabID); } }); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h old mode 100644 new mode 100755 index 2889e5ffa3..d87f00e1ea --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -54,7 +54,6 @@ #include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" -#include "Grab.h" #include @@ -67,8 +66,6 @@ using PackedAvatarEntityMap = QMap; // similar to AvatarEntit using AvatarEntityIDs = QSet; using AvatarGrabDataMap = QMap; -using AvatarGrabIDs = QSet; -using AvatarGrabMap = QMap; using AvatarDataSequenceNumber = uint16_t; @@ -496,6 +493,8 @@ public: /// \return number of bytes parsed virtual int parseDataFromBuffer(const QByteArray& buffer); + virtual void setCollisionWithOtherAvatarsFlags() {}; + // Body Rotation (degrees) float getBodyYaw() const; void setBodyYaw(float bodyYaw); @@ -1473,8 +1472,6 @@ protected: mutable ReadWriteLockable _avatarGrabsLock; AvatarGrabDataMap _avatarGrabData; bool _avatarGrabDataChanged { false }; // by network - AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics - AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() }; @@ -1537,7 +1534,7 @@ protected: } bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData); - void clearAvatarGrabData(const QUuid& grabID); + virtual void clearAvatarGrabData(const QUuid& grabID); private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 3c01ee0942..f2a5ca1296 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -15,6 +15,11 @@ namespace controller { +enum class HmdAvatarAlignmentType { + Eyes = 0, // align the user's eyes with the avatars eyes + Head // align the user's head with the avatars head +}; + struct InputCalibrationData { glm::mat4 sensorToWorldMat; // sensor to world glm::mat4 avatarMat; // avatar to world @@ -29,6 +34,7 @@ struct InputCalibrationData { glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space + HmdAvatarAlignmentType hmdAvatarAlignmentType; }; enum class ChannelType { diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index a061a4c923..944d5e89d1 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -31,6 +31,8 @@ public: virtual void compositeExtra() override; + virtual void pluginUpdate() override {}; + protected: mutable bool _isThrottled = false; diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 11563b3798..e4ff1b8b37 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -20,6 +20,7 @@ public: QImage getScreenshot(float aspectRatio = 0.0f) const override; QImage getSecondaryCameraScreenshot() const override; void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {}; + void pluginUpdate() override {}; private: static const QString NAME; }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 5835316efe..6c59dbd861 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -15,8 +15,10 @@ #include #include #include +#include #include +#include #include #include @@ -30,6 +32,7 @@ #include #include +#include #include #include #include @@ -465,11 +468,43 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) { }); } -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor) { - renderFromTexture(batch, texture, viewport, scissor, gpu::FramebufferPointer()); +void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const { + withOtherThreadContext([&] { + using namespace gpu; + auto glBackend = const_cast(*this).getGLBackend(); + FramebufferPointer framebuffer{ Framebuffer::create("captureFramebuffer") }; + TextureCapturer captureLambda = [&](const std::string& filename, const gpu::TexturePointer& texture, uint16 layer) { + QImage image; + if (texture->getUsageType() == TextureUsageType::STRICT_RESOURCE) { + image = QImage{ 1, 1, QImage::Format_ARGB32 }; + auto storedImage = texture->accessStoredMipFace(0, 0); + memcpy(image.bits(), storedImage->data(), image.sizeInBytes()); + //if (texture == textureCache->getWhiteTexture()) { + //} else if (texture == textureCache->getBlackTexture()) { + //} else if (texture == textureCache->getBlueTexture()) { + //} else if (texture == textureCache->getGrayTexture()) { + } else { + ivec4 rect = { 0, 0, texture->getWidth(), texture->getHeight() }; + framebuffer->setRenderBuffer(0, texture, layer); + glBackend->syncGPUObject(*framebuffer); + + image = QImage{ rect.z, rect.w, QImage::Format_ARGB32 }; + glBackend->downloadFramebuffer(framebuffer, rect, image); + } + QImageWriter(filename.c_str()).write(image); + }; + + if (_currentFrame) { + gpu::writeFrame(filename, _currentFrame, captureLambda); + } + }); } -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer copyFbo /*=gpu::FramebufferPointer()*/) { +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) { + renderFromTexture(batch, texture, viewport, scissor, nullptr); +} + +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { auto fbo = gpu::FramebufferPointer(); batch.enableStereo(false); batch.resetViewTransform(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 1cc060161b..5c653f8a0a 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -47,7 +47,7 @@ public: void endSession() override final; bool eventFilter(QObject* receiver, QEvent* event) override; bool isDisplayVisible() const override { return true; } - + void captureFrame(const std::string& outputName) const override; void submitFrame(const gpu::FramePointer& newFrame) override; glm::uvec2 getRecommendedRenderSize() const override { @@ -113,8 +113,8 @@ protected: // Plugin specific functionality to send the composed scene to the output window or device virtual void internalPresent(); - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer fbo); - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor); virtual void updateFrameData(); virtual glm::mat4 getViewCorrection() { return glm::mat4(); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index 40063652c8..9eafe381ab 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -69,7 +69,10 @@ bool DebugHmdDisplayPlugin::internalActivate() { _eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); _eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, -0.0149999997, 1.0 }; _eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, -0.0149999997, 1.0 }; - _renderTargetSize = { 3024, 1680 }; + // Test HMD per-eye resolution + _renderTargetSize = uvec2{ 1214 * 2 , 1344 }; + // uncomment to capture a quarter size frame + //_renderTargetSize /= 2; _cullingProjection = _eyeProjections[0]; // This must come after the initialization, so that the values calculated // above are available during the customizeContext call (when not running diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index a56daaad83..4aeacbe05c 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -46,6 +46,8 @@ public: virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; }; + void pluginUpdate() override {}; + signals: void hmdMountedChanged(); void hmdVisibleChanged(bool visible); diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index 5a7ca24059..a55bde0f4e 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -28,6 +28,8 @@ public: // to the HMD plugins. //virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; + virtual void pluginUpdate() override {}; + protected: virtual bool internalActivate() override; virtual void internalDeactivate() override; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index a194f4d4b7..83f0bdcff3 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -141,7 +141,7 @@ std::shared_ptr make_renderer(const EntityItemPointer& entity) { return std::shared_ptr(new T(entity), [](T* ptr) { ptr->deleteLater(); }); } -EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity), _created(entity->getCreated()) { +EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity) { connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] { _needsRenderUpdate = true; emit requestRenderUpdate(); @@ -479,7 +479,7 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls } float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND); - float pulse = 0.5f * (glm::cos(t * (2.0f * M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); + float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); float outPulse = (1.0f - pulse); glm::vec4 result = color; diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp index 814a4d8ab3..1a188ca163 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp @@ -286,4 +286,4 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) { } } } -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index a959131f7d..192c6cfd49 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -42,10 +42,23 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare // TODO: move into Procedural.cpp PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); PrepareStencil::testMask(*_procedural._transparentState); + + addMaterial(graphics::MaterialLayer(_material, 0), "0"); } bool ShapeEntityRenderer::needsRenderUpdate() const { - if (_procedural.isEnabled() && _procedural.isFading()) { + if (resultWithReadLock([&] { + if (_procedural.isEnabled() && _procedural.isFading()) { + return true; + } + + auto mat = _materials.find("0"); + if (mat != _materials.end() && mat->second.needsUpdate()) { + return true; + } + + return false; + })) { return true; } @@ -56,7 +69,11 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin if (_lastUserData != entity->getUserData()) { return true; } - if (_material != entity->getMaterial()) { + + if (_color != entity->getColor()) { + return true; + } + if (_alpha != entity->getAlpha()) { return true; } @@ -83,10 +100,6 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _procedural.setProceduralData(ProceduralData::parse(_lastUserData)); } - removeMaterial(_material, "0"); - _material = entity->getMaterial(); - addMaterial(graphics::MaterialLayer(_material, 0), "0"); - _shape = entity->getShape(); _pulseProperties = entity->getPulseProperties(); }); @@ -116,6 +129,20 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint _procedural.setIsFading(isFading); } }); + + glm::u8vec3 color = entity->getColor(); + float alpha = entity->getAlpha(); + if (_color != color || _alpha != alpha) { + _color = color; + _alpha = alpha; + _material->setAlbedo(toGlm(_color)); + _material->setOpacity(_alpha); + + auto materials = _materials.find("0"); + if (materials != _materials.end()) { + materials->second.setNeedsUpdate(true); + } + } } bool ShapeEntityRenderer::isTransparent() const { @@ -129,18 +156,15 @@ bool ShapeEntityRenderer::isTransparent() const { auto mat = _materials.find("0"); if (mat != _materials.end()) { - if (mat->second.top().material) { - auto matKey = mat->second.top().material->getKey(); - if (matKey.isTranslucent()) { - return true; - } + if (mat->second.getMaterialKey().isTranslucent()) { + return true; } } return Parent::isTransparent(); } -bool ShapeEntityRenderer::useMaterialPipeline() const { +bool ShapeEntityRenderer::useMaterialPipeline(const graphics::MultiMaterial& materials) const { bool proceduralReady = resultWithReadLock([&] { return _procedural.isReady(); }); @@ -148,12 +172,7 @@ bool ShapeEntityRenderer::useMaterialPipeline() const { return false; } - graphics::MaterialKey drawMaterialKey; - auto mat = _materials.find("0"); - if (mat != _materials.end() && mat->second.top().material) { - drawMaterialKey = mat->second.top().material->getKey(); - } - + graphics::MaterialKey drawMaterialKey = materials.getMaterialKey(); if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) { return true; } @@ -168,11 +187,13 @@ bool ShapeEntityRenderer::useMaterialPipeline() const { } ShapeKey ShapeEntityRenderer::getShapeKey() { - if (useMaterialPipeline()) { - graphics::MaterialKey drawMaterialKey; - if (_materials["0"].top().material) { - drawMaterialKey = _materials["0"].top().material->getKey(); - } + auto mat = _materials.find("0"); + if (mat != _materials.end() && mat->second.needsUpdate()) { + RenderPipelines::updateMultiMaterial(mat->second); + } + + if (mat != _materials.end() && useMaterialPipeline(mat->second)) { + graphics::MaterialKey drawMaterialKey = mat->second.getMaterialKey(); bool isTranslucent = drawMaterialKey.isTranslucent(); bool hasTangents = drawMaterialKey.isNormalMap(); @@ -225,7 +246,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; - std::shared_ptr mat; + graphics::MultiMaterial materials; auto geometryCache = DependencyManager::get(); GeometryCache::Shape geometryShape; bool proceduralRender = false; @@ -233,30 +254,25 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { withReadLock([&] { geometryShape = geometryCache->getShapeForEntityShape(_shape); batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation - mat = _materials["0"].top().material; - if (mat) { - outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity()); - outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created); - if (_procedural.isReady()) { - outColor = _procedural.getColor(outColor); - outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; - _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); - proceduralRender = true; - } + materials = _materials["0"]; + auto& schema = materials.getSchemaBuffer().get(); + outColor = glm::vec4(schema._albedo, schema._opacity); + outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created); + if (_procedural.isReady()) { + outColor = _procedural.getColor(outColor); + outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; + _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); + proceduralRender = true; } }); - if (!mat) { - return; - } - if (proceduralRender) { if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { geometryCache->renderWireShape(batch, geometryShape, outColor); } else { geometryCache->renderShape(batch, geometryShape, outColor); } - } else if (!useMaterialPipeline()) { + } else if (!useMaterialPipeline(materials)) { // FIXME, support instanced multi-shape rendering using multidraw indirect outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; render::ShapePipelinePointer pipeline; @@ -272,7 +288,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } } else { if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { - RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing); + RenderPipelines::bindMaterials(materials, batch, args->_enableTexturing); args->_details._materialSwitches++; } @@ -291,8 +307,9 @@ scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel() { { std::lock_guard lock(_materialsLock); result.appendMaterials(_materials); - if (_materials["0"].top().material) { - vertexColor = _materials["0"].top().material->getAlbedo(); + auto materials = _materials.find("0"); + if (materials != _materials.end()) { + vertexColor = materials->second.getSchemaBuffer().get()._albedo; } } if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 0efcdbb360..c9fd4801d5 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -35,13 +35,17 @@ private: virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; - bool useMaterialPipeline() const; + bool useMaterialPipeline(const graphics::MultiMaterial& materials) const; Procedural _procedural; QString _lastUserData; entity::Shape _shape { entity::Sphere }; + PulsePropertyGroup _pulseProperties; - std::shared_ptr _material; + std::shared_ptr _material { std::make_shared() }; + glm::u8vec3 _color; + float _alpha; + glm::vec3 _position; glm::vec3 _dimensions; glm::quat _orientation; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 32c7eb29dd..41e4f43a5d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -3425,7 +3425,19 @@ void EntityItem::addGrab(GrabPointer grab) { enableNoBootstrap(); SpatiallyNestable::addGrab(grab); - if (getDynamic() && getParentID().isNull()) { + if (!getParentID().isNull()) { + return; + } + + int jointIndex = grab->getParentJointIndex(); + bool isFarGrab = jointIndex == FARGRAB_RIGHTHAND_INDEX + || jointIndex == FARGRAB_LEFTHAND_INDEX + || jointIndex == FARGRAB_MOUSE_INDEX; + + // GRAB HACK: FarGrab doesn't work on non-dynamic things yet, but we really really want NearGrab + // (aka Hold) to work for such ojects, hence we filter the useAction case like so: + bool useAction = getDynamic() || (_physicsInfo && !isFarGrab); + if (useAction) { EntityTreePointer entityTree = getTree(); assert(entityTree); EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; @@ -3436,13 +3448,11 @@ void EntityItem::addGrab(GrabPointer grab) { EntityDynamicType dynamicType; QVariantMap arguments; - int grabParentJointIndex =grab->getParentJointIndex(); - if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX || - grabParentJointIndex == FARGRAB_MOUSE_INDEX) { + if (isFarGrab) { // add a far-grab action dynamicType = DYNAMIC_TYPE_FAR_GRAB; arguments["otherID"] = grab->getOwnerID(); - arguments["otherJointIndex"] = grabParentJointIndex; + arguments["otherJointIndex"] = jointIndex; arguments["targetPosition"] = vec3ToQMap(grab->getPositionalOffset()); arguments["targetRotation"] = quatToQMap(grab->getRotationalOffset()); arguments["linearTimeScale"] = 0.05; @@ -3463,11 +3473,23 @@ void EntityItem::addGrab(GrabPointer grab) { grab->setActionID(actionID); _grabActions[actionID] = action; simulation->addDynamic(action); + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); + simulation->changeEntity(getThisPointer()); } } void EntityItem::removeGrab(GrabPointer grab) { + int oldNumGrabs = _grabs.size(); SpatiallyNestable::removeGrab(grab); + if (!getDynamic() && _grabs.size() != oldNumGrabs) { + // GRAB HACK: the expected behavior is for non-dynamic grabbed things to NOT be throwable + // so we slam the velocities to zero here whenever the number of grabs change. + // NOTE: if there is still another grab in effect this shouldn't interfere with the object's motion + // because that grab will slam the position+velocities next frame. + setLocalVelocity(glm::vec3(0.0f)); + setAngularVelocity(glm::vec3(0.0f)); + } + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); QUuid actionID = grab->getActionID(); if (!actionID.isNull()) { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3b6bdc44b8..0c8396f405 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1023,7 +1023,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * parse the JSON string into a JavaScript object of name, URL pairs. Read-only. * * @property {ShapeType} shapeType="none" - The shape of the collision hull used if collisions are enabled. - * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is * "compound". * * @property {Entities.AnimationProperties} animation - An animation to play on the model. @@ -1373,7 +1373,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar * permissions have effect. Reverts to the default value if set to "none", or set to "compound" * and compoundShapeURL is "". - * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is * "compound". * * @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:
diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 5d3f11cdc9..869ae2985f 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -112,7 +112,6 @@ EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, c ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Shape; _volumeMultiplier *= PI / 6.0f; - _material = std::make_shared(); } EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { @@ -234,7 +233,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit void ShapeEntityItem::setColor(const glm::u8vec3& value) { withWriteLock([&] { _color = value; - _material->setAlbedo(toGlm(_color)); }); } @@ -247,7 +245,6 @@ glm::u8vec3 ShapeEntityItem::getColor() const { void ShapeEntityItem::setAlpha(float alpha) { withWriteLock([&] { _alpha = alpha; - _material->setOpacity(alpha); }); } diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index bd0f65d9e2..28edf2e1a2 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -101,8 +101,6 @@ public: virtual void computeShapeInfo(ShapeInfo& info) override; virtual ShapeType getShapeType() const override; - std::shared_ptr getMaterial() { return _material; } - PulsePropertyGroup getPulseProperties() const; protected: @@ -115,8 +113,6 @@ protected: //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; - - std::shared_ptr _material; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index bd444d28dd..f574234354 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -96,11 +96,11 @@ const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. 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 = 255; // 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 uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = PERSONAL_SIMULATION_PRIORITY; class SimulationOwner { diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 68019c2f4b..38465d2809 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -131,6 +131,7 @@ public: glm::vec3 geometricTranslation; glm::quat geometricRotation; glm::vec3 geometricScaling; + bool isLimbNode; // is this FBXModel transform is a "LimbNode" i.e. a joint }; glm::mat4 getGlobalTransform(const QMultiMap& _connectionParentMap, @@ -559,9 +560,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr glm::vec3 geometricRotation; glm::vec3 rotationMin, rotationMax; + + bool isLimbNode = object.properties.size() >= 3 && object.properties.at(2) == "LimbNode"; FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), - glm::mat4(), glm::vec3(), glm::vec3(), - false, glm::vec3(), glm::quat(), glm::vec3(1.0f) }; + glm::mat4(), glm::vec3(), glm::vec3(), + false, glm::vec3(), glm::quat(), glm::vec3(1.0f), isLimbNode }; ExtractedMesh* mesh = NULL; QVector blendshapes; foreach (const FBXNode& subobject, object.children) { @@ -752,17 +755,17 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } else if (subobject.name == "Texture_Alpha_Source" && subobject.properties.length() >= TEXTURE_ALPHA_SOURCE_MIN_SIZE) { tex.assign(tex.alphaSource, subobject.properties.at(0).value()); } else if (subobject.name == "ModelUVTranslation" && subobject.properties.length() >= MODEL_UV_TRANSLATION_MIN_SIZE) { - tex.assign(tex.UVTranslation, glm::vec2(subobject.properties.at(0).value(), - subobject.properties.at(1).value())); + auto newTranslation = glm::vec3(subobject.properties.at(0).value(), subobject.properties.at(1).value(), 0.0); + tex.assign(tex.translation, tex.translation + newTranslation); } else if (subobject.name == "ModelUVScaling" && subobject.properties.length() >= MODEL_UV_SCALING_MIN_SIZE) { - tex.assign(tex.UVScaling, glm::vec2(subobject.properties.at(0).value(), - subobject.properties.at(1).value())); - if (tex.UVScaling.x == 0.0f) { - tex.UVScaling.x = 1.0f; + auto newScaling = glm::vec3(subobject.properties.at(0).value(), subobject.properties.at(1).value(), 1.0); + if (newScaling.x == 0.0f) { + newScaling.x = 1.0f; } - if (tex.UVScaling.y == 0.0f) { - tex.UVScaling.y = 1.0f; + if (newScaling.y == 0.0f) { + newScaling.y = 1.0f; } + tex.assign(tex.scaling, tex.scaling * newScaling); } else if (subobject.name == "Cropping" && subobject.properties.length() >= CROPPING_MIN_SIZE) { tex.assign(tex.cropping, glm::vec4(subobject.properties.at(0).value(), subobject.properties.at(1).value(), @@ -790,20 +793,21 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } else if (property.properties.at(0) == USE_MATERIAL) { tex.assign(tex.useMaterial, property.properties.at(index).value()); } else if (property.properties.at(0) == TRANSLATION) { - tex.assign(tex.translation, getVec3(property.properties, index)); + tex.assign(tex.translation, tex.translation + getVec3(property.properties, index)); } else if (property.properties.at(0) == ROTATION) { tex.assign(tex.rotation, getVec3(property.properties, index)); } else if (property.properties.at(0) == SCALING) { - tex.assign(tex.scaling, getVec3(property.properties, index)); - if (tex.scaling.x == 0.0f) { - tex.scaling.x = 1.0f; + auto newScaling = getVec3(property.properties, index); + if (newScaling.x == 0.0f) { + newScaling.x = 1.0f; } - if (tex.scaling.y == 0.0f) { - tex.scaling.y = 1.0f; + if (newScaling.y == 0.0f) { + newScaling.y = 1.0f; } - if (tex.scaling.z == 0.0f) { - tex.scaling.z = 1.0f; + if (newScaling.z == 0.0f) { + newScaling.z = 1.0f; } + tex.assign(tex.scaling, tex.scaling * newScaling); } #if defined(DEBUG_FBXSERIALIZER) else { @@ -848,6 +852,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } else if (object.name == "Material") { HFMMaterial material; + MaterialParam materialParam; material.name = (object.properties.at(1).toString()); foreach (const FBXNode& subobject, object.children) { bool properties = false; @@ -892,6 +897,10 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr static const QVariant MAYA_EMISSIVE_INTENSITY = QByteArray("Maya|emissive_intensity"); static const QVariant MAYA_USE_EMISSIVE_MAP = QByteArray("Maya|use_emissive_map"); static const QVariant MAYA_USE_AO_MAP = QByteArray("Maya|use_ao_map"); + static const QVariant MAYA_UV_SCALE = QByteArray("Maya|uv_scale"); + static const QVariant MAYA_UV_OFFSET = QByteArray("Maya|uv_offset"); + static const int MAYA_UV_OFFSET_PROPERTY_LENGTH = 6; + static const int MAYA_UV_SCALE_PROPERTY_LENGTH = 6; @@ -980,6 +989,27 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr material.isPBSMaterial = true; material.useOcclusionMap = (bool)property.properties.at(index).value(); + } else if (property.properties.at(0) == MAYA_UV_SCALE) { + if (property.properties.size() == MAYA_UV_SCALE_PROPERTY_LENGTH) { + // properties: { "Maya|uv_scale", "Vector2D", "Vector2", nothing, double, double } + glm::vec3 scale = glm::vec3(property.properties.at(4).value(), property.properties.at(5).value(), 1.0); + if (scale.x == 0.0f) { + scale.x = 1.0f; + } + if (scale.y == 0.0f) { + scale.y = 1.0f; + } + if (scale.z == 0.0f) { + scale.z = 1.0f; + } + materialParam.scaling *= scale; + } + } else if (property.properties.at(0) == MAYA_UV_OFFSET) { + if (property.properties.size() == MAYA_UV_OFFSET_PROPERTY_LENGTH) { + // properties: { "Maya|uv_offset", "Vector2D", "Vector2", nothing, double, double } + glm::vec3 translation = glm::vec3(property.properties.at(4).value(), property.properties.at(5).value(), 1.0); + materialParam.translation += translation; + } } else { const QString propname = property.properties.at(0).toString(); unknowns.push_back(propname.toStdString()); @@ -1001,6 +1031,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } material.materialID = getID(object.properties); _hfmMaterials.insert(material.materialID, material); + _materialParams.insert(material.materialID, materialParam); } else if (object.name == "NodeAttribute") { @@ -1258,6 +1289,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr // convert the models to joints QVariantList freeJoints = mapping.values("freeJoint"); hfmModel.hasSkeletonJoints = false; + foreach (const QString& modelID, modelIDs) { const FBXModel& fbxModel = fbxModels[modelID]; HFMJoint joint; @@ -1288,6 +1320,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.geometricTranslation = fbxModel.geometricTranslation; joint.geometricRotation = fbxModel.geometricRotation; joint.geometricScaling = fbxModel.geometricScaling; + joint.isSkeletonJoint = fbxModel.isLimbNode; + hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; @@ -1311,14 +1345,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); } - foreach (const QString& childID, _connectionChildMap.values(modelID)) { - QString type = typeFlags.value(childID); - if (!type.isEmpty()) { - hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); - break; - } - } - joint.bindTransformFoundInCluster = false; hfmModel.joints.append(joint); @@ -1439,7 +1465,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr materialIndex++; } else if (_textureFilenames.contains(childID)) { - HFMTexture texture = getTexture(childID); + // NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale") + // I'm leaving the second parameter blank right now as this code may never be used. + HFMTexture texture = getTexture(childID, ""); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { int partTexture = extracted.partMaterialTextures.at(j).second; if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { @@ -1452,9 +1480,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - extracted.mesh.createMeshTangents(generateTangents); - extracted.mesh.createBlendShapeTangents(generateTangents); - // find the clusters with which the mesh is associated QVector clusterIDs; foreach (const QString& childID, _connectionChildMap.values(it.key())) { diff --git a/libraries/fbx/src/FBXSerializer.h b/libraries/fbx/src/FBXSerializer.h index 31ca301522..b95bb729e7 100644 --- a/libraries/fbx/src/FBXSerializer.h +++ b/libraries/fbx/src/FBXSerializer.h @@ -37,8 +37,6 @@ class FBXNode; class TextureParam { public: - glm::vec2 UVTranslation; - glm::vec2 UVScaling; glm::vec4 cropping; QString UVSet; @@ -63,8 +61,6 @@ public: bool isDefault; TextureParam() : - UVTranslation(0.0f), - UVScaling(1.0f), cropping(0.0f), UVSet("map1"), translation(0.0f), @@ -77,8 +73,6 @@ public: {} TextureParam(const TextureParam& src) : - UVTranslation(src.UVTranslation), - UVScaling(src.UVScaling), cropping(src.cropping), UVSet(src.UVSet), translation(src.translation), @@ -92,6 +86,22 @@ public: }; +class MaterialParam { +public: + glm::vec3 translation; + glm::vec3 scaling; + + MaterialParam() : + translation(0.0), + scaling(1.0) + {} + + MaterialParam(const MaterialParam& src) : + translation(src.translation), + scaling(src.scaling) + {} +}; + class ExtractedMesh; class FBXSerializer : public HFMSerializer { @@ -114,7 +124,7 @@ public: static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate = true); QHash meshes; - HFMTexture getTexture(const QString& textureID); + HFMTexture getTexture(const QString& textureID, const QString& materialID); QHash _textureNames; // Hashes the original RelativeFilename of textures @@ -141,6 +151,7 @@ public: QHash occlusionTextures; QHash _hfmMaterials; + QHash _materialParams; void consolidateHFMMaterials(const QVariantHash& mapping); diff --git a/libraries/fbx/src/FBXSerializer_Material.cpp b/libraries/fbx/src/FBXSerializer_Material.cpp index 7713b36e57..0a1d15b72a 100644 --- a/libraries/fbx/src/FBXSerializer_Material.cpp +++ b/libraries/fbx/src/FBXSerializer_Material.cpp @@ -27,7 +27,7 @@ #include -HFMTexture FBXSerializer::getTexture(const QString& textureID) { +HFMTexture FBXSerializer::getTexture(const QString& textureID, const QString& materialID) { HFMTexture texture; const QByteArray& filepath = _textureFilepaths.value(textureID); texture.content = _textureContent.value(filepath); @@ -45,8 +45,8 @@ HFMTexture FBXSerializer::getTexture(const QString& textureID) { if (_textureParams.contains(textureID)) { auto p = _textureParams.value(textureID); - texture.transform.setTranslation(p.translation); - texture.transform.setRotation(glm::quat(glm::radians(p.rotation))); + texture.transform.postTranslate(p.translation); + texture.transform.postRotate(glm::quat(glm::radians(p.rotation))); auto scaling = p.scaling; // Protect from bad scaling which should never happen @@ -59,13 +59,19 @@ HFMTexture FBXSerializer::getTexture(const QString& textureID) { if (scaling.z == 0.0f) { scaling.z = 1.0f; } - texture.transform.setScale(scaling); + texture.transform.postScale(scaling); if ((p.UVSet != "map1") && (p.UVSet != "UVSet0")) { texture.texcoordSet = 1; } texture.texcoordSetName = p.UVSet; } + auto materialParamItr = _materialParams.find(materialID); + if (materialParamItr != _materialParams.end()) { + auto& materialParam = materialParamItr.value(); + texture.transform.postTranslate(materialParam.translation); + texture.transform.postScale(materialParam.scaling); + } return texture; } @@ -102,12 +108,12 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { material.diffuseFactor = 1.0; } - diffuseTexture = getTexture(diffuseTextureID); + diffuseTexture = getTexture(diffuseTextureID, material.materialID); // FBX files generated by 3DSMax have an intermediate texture parent, apparently foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { if (_textureFilenames.contains(childTextureID)) { - diffuseTexture = getTexture(diffuseTextureID); + diffuseTexture = getTexture(diffuseTextureID, material.materialID); } } @@ -122,7 +128,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { transparentTextureID = diffuseTextureID; } if (!transparentTextureID.isNull()) { - transparentTexture = getTexture(transparentTextureID); + transparentTexture = getTexture(transparentTextureID, material.materialID); material.opacityTexture = transparentTexture; detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity()); } @@ -131,13 +137,13 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { QString bumpTextureID = bumpTextures.value(material.materialID); QString normalTextureID = normalTextures.value(material.materialID); if (!normalTextureID.isNull()) { - normalTexture = getTexture(normalTextureID); + normalTexture = getTexture(normalTextureID, material.materialID); normalTexture.isBumpmap = false; material.normalTexture = normalTexture; detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity()); } else if (!bumpTextureID.isNull()) { - normalTexture = getTexture(bumpTextureID); + normalTexture = getTexture(bumpTextureID, material.materialID); normalTexture.isBumpmap = true; material.normalTexture = normalTexture; @@ -147,7 +153,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { HFMTexture specularTexture; QString specularTextureID = specularTextures.value(material.materialID); if (!specularTextureID.isNull()) { - specularTexture = getTexture(specularTextureID); + specularTexture = getTexture(specularTextureID, material.materialID); detectDifferentUVs |= (specularTexture.texcoordSet != 0) || (!specularTexture.transform.isIdentity()); material.specularTexture = specularTexture; } @@ -155,7 +161,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { HFMTexture metallicTexture; QString metallicTextureID = metallicTextures.value(material.materialID); if (!metallicTextureID.isNull()) { - metallicTexture = getTexture(metallicTextureID); + metallicTexture = getTexture(metallicTextureID, material.materialID); detectDifferentUVs |= (metallicTexture.texcoordSet != 0) || (!metallicTexture.transform.isIdentity()); material.metallicTexture = metallicTexture; } @@ -163,7 +169,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { HFMTexture roughnessTexture; QString roughnessTextureID = roughnessTextures.value(material.materialID); if (!roughnessTextureID.isNull()) { - roughnessTexture = getTexture(roughnessTextureID); + roughnessTexture = getTexture(roughnessTextureID, material.materialID); material.roughnessTexture = roughnessTexture; detectDifferentUVs |= (roughnessTexture.texcoordSet != 0) || (!roughnessTexture.transform.isIdentity()); } @@ -171,7 +177,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { HFMTexture shininessTexture; QString shininessTextureID = shininessTextures.value(material.materialID); if (!shininessTextureID.isNull()) { - shininessTexture = getTexture(shininessTextureID); + shininessTexture = getTexture(shininessTextureID, material.materialID); material.glossTexture = shininessTexture; detectDifferentUVs |= (shininessTexture.texcoordSet != 0) || (!shininessTexture.transform.isIdentity()); } @@ -179,7 +185,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { HFMTexture emissiveTexture; QString emissiveTextureID = emissiveTextures.value(material.materialID); if (!emissiveTextureID.isNull()) { - emissiveTexture = getTexture(emissiveTextureID); + emissiveTexture = getTexture(emissiveTextureID, material.materialID); detectDifferentUVs |= (emissiveTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity()); material.emissiveTexture = emissiveTexture; @@ -202,7 +208,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { } if (!occlusionTextureID.isNull()) { - occlusionTexture = getTexture(occlusionTextureID); + occlusionTexture = getTexture(occlusionTextureID, material.materialID); detectDifferentUVs |= (occlusionTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity()); material.occlusionTexture = occlusionTexture; } @@ -222,7 +228,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { } if (_loadLightmaps && !ambientTextureID.isNull()) { - ambientTexture = getTexture(ambientTextureID); + ambientTexture = getTexture(ambientTextureID, material.materialID); detectDifferentUVs |= (ambientTexture.texcoordSet != 0) || (!ambientTexture.transform.isIdentity()); material.lightmapTexture = ambientTexture; material.lightmapParams = lightmapParams; diff --git a/libraries/gl/src/gl/Config.cpp b/libraries/gl/src/gl/Config.cpp index 0e3a174e91..94bb91a3e9 100644 --- a/libraries/gl/src/gl/Config.cpp +++ b/libraries/gl/src/gl/Config.cpp @@ -118,6 +118,8 @@ void gl::setSwapInterval(int interval) { wglSwapIntervalEXT(interval); #elif defined(Q_OS_MAC) CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#elif defined(Q_OS_ANDROID) + eglSwapInterval(eglGetCurrentDisplay(), interval); #else Q_UNUSED(interval); #endif diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 371e6454e6..7d27b42909 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -34,22 +34,9 @@ bool Context::USE_CUSTOM_CONTEXT { true }; #endif bool Context::enableDebugLogger() { -#if defined(Q_OS_MAC) - // OSX does not support GL_KHR_debug or GL_ARB_debug_output - return false; -#else -#if defined(DEBUG) || defined(USE_GLES) - static bool enableDebugLogger = true; -#else - static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); - static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -#endif - return enableDebugLogger; -#endif + return gl::debugContextEnabled(); } - - std::atomic Context::_totalSwapchainMemoryUsage { 0 }; size_t Context::getSwapchainMemoryUsage() { return _totalSwapchainMemoryUsage.load(); } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index b6b6ad3596..82724dfd62 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -52,6 +52,11 @@ void Context::moveToThread(QThread* thread) { } void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { + auto type = debugMessage.type(); + if (type == QOpenGLDebugMessage::PerformanceType) { + return; + } + auto severity = debugMessage.severity(); switch (severity) { case QOpenGLDebugMessage::NotificationSeverity: @@ -60,13 +65,13 @@ void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { default: break; } - qDebug(glLogging) << debugMessage; + qWarning(glLogging) << debugMessage; return; } void Context::setupDebugLogging(QOpenGLContext *context) { QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context); - QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){ + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, context, [](const QOpenGLDebugMessage& message){ Context::debugMessageHandler(message); }); if (logger->initialize()) { diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 15a41c3dc1..2c02fdca03 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -198,11 +198,48 @@ namespace gl { bool checkGLErrorDebug(const char* name) { -#ifdef DEBUG + // Disabling error checking macro on Android debug builds for now, + // as it throws off performance testing, which must be done on + // Debug builds +#if defined(DEBUG) && !defined(Q_OS_ANDROID) return checkGLError(name); #else Q_UNUSED(name); return false; #endif } + + // Enables annotation of captures made by tools like renderdoc + bool khrDebugEnabled() { + static std::once_flag once; + static bool khrDebug = false; + std::call_once(once, [&] { + khrDebug = nullptr != glPushDebugGroupKHR; + }); + return khrDebug; + } + + // Enables annotation of captures made by tools like renderdoc + bool extDebugMarkerEnabled() { + static std::once_flag once; + static bool extMarker = false; + std::call_once(once, [&] { + extMarker = nullptr != glPushGroupMarkerEXT; + }); + return extMarker; + } + + bool debugContextEnabled() { +#if defined(Q_OS_MAC) + // OSX does not support GL_KHR_debug or GL_ARB_debug_output + static bool enableDebugLogger = false; +#elif defined(DEBUG) || defined(USE_GLES) + //static bool enableDebugLogger = true; + static bool enableDebugLogger = false; +#else + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); + static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); +#endif + return enableDebugLogger; + } } diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 1865442ef6..2a798e6e98 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -37,7 +37,13 @@ bool isRenderThread(); namespace gl { void globalLock(); void globalRelease(bool finish = true); - + + bool debugContextEnabled(); + + bool khrDebugEnabled(); + + bool extDebugMarkerEnabled(); + void withSavedContext(const std::function& f); bool checkGLError(const char* name); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 295b9becb8..82f4f97e3b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -392,8 +392,38 @@ void GLBackend::renderPassDraw(const Batch& batch) { } } +// Support annotating captures in tools like Renderdoc +class GlDuration { +public: +#ifdef USE_GLES + GlDuration(const char* name) { + // We need to use strlen here instead of -1, because the Snapdragon profiler + // will crash otherwise + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, strlen(name), name); + } + ~GlDuration() { + glPopDebugGroup(); + } +#else + GlDuration(const char* name) { + if (::gl::khrDebugEnabled()) { + glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 0, -1, name); + } + } + ~GlDuration() { + if (::gl::khrDebugEnabled()) { + glPopDebugGroupKHR(); + } + } +#endif +}; + +#define GL_PROFILE_RANGE(category, name) \ + PROFILE_RANGE(category, name); \ + GlDuration glProfileRangeThis(name); + void GLBackend::render(const Batch& batch) { - PROFILE_RANGE(render_gpu_gl, batch.getName()); + GL_PROFILE_RANGE(render_gpu_gl, batch.getName().c_str()); _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); // Allow the batch to override the rendering stereo settings @@ -406,7 +436,7 @@ void GLBackend::render(const Batch& batch) { _transform._projectionJitter = Vec2(0.0f, 0.0f); { - PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); + GL_PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); renderPassTransfer(batch); } @@ -416,7 +446,7 @@ void GLBackend::render(const Batch& batch) { } #endif { - PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render"); + GL_PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render"); renderPassDraw(batch); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 07f7df9277..b5a279a54c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -54,153 +54,8 @@ #define GPU_STEREO_CAMERA_BUFFER #endif -// -// GL Backend pointer storage mechanism -// One of the following three defines must be defined. -// GPU_POINTER_STORAGE_SHARED - -// The platonic ideal, use references to smart pointers. -// However, this produces artifacts because there are too many places in the code right now that -// create temporary values (undesirable smart pointer duplications) and then those temp variables -// get passed on and have their reference taken, and then invalidated -// GPU_POINTER_STORAGE_REF - -// Raw pointer manipulation. Seems more dangerous than the reference wrappers, -// but in practice, the danger of grabbing a reference to a temporary variable -// is causing issues -// GPU_POINTER_STORAGE_RAW - -#if defined(USE_GLES) -#define GPU_POINTER_STORAGE_SHARED -#else -#define GPU_POINTER_STORAGE_RAW -#endif - namespace gpu { namespace gl { -#if defined(GPU_POINTER_STORAGE_SHARED) -template -static inline bool compare(const std::shared_ptr& a, const std::shared_ptr& b) { - return a == b; -} - -template -static inline T* acquire(const std::shared_ptr& pointer) { - return pointer.get(); -} - -template -static inline void reset(std::shared_ptr& pointer) { - return pointer.reset(); -} - -template -static inline bool valid(const std::shared_ptr& pointer) { - return pointer.operator bool(); -} - -template -static inline void assign(std::shared_ptr& pointer, const std::shared_ptr& source) { - pointer = source; -} - -using BufferReference = BufferPointer; -using TextureReference = TexturePointer; -using FramebufferReference = FramebufferPointer; -using FormatReference = Stream::FormatPointer; -using PipelineReference = PipelinePointer; - -#define GPU_REFERENCE_INIT_VALUE nullptr - -#elif defined(GPU_POINTER_STORAGE_REF) - -template -class PointerReferenceWrapper : public std::reference_wrapper> { - using Parent = std::reference_wrapper>; - -public: - using Pointer = std::shared_ptr; - PointerReferenceWrapper() : Parent(EMPTY()) {} - PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {} - void clear() { *this = EMPTY(); } - -private: - static const Pointer& EMPTY() { - static const Pointer EMPTY_VALUE; - return EMPTY_VALUE; - }; -}; - -template -static bool compare(const PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { - return reference.get() == pointer; -} - -template -static inline T* acquire(const PointerReferenceWrapper& reference) { - return reference.get().get(); -} - -template -static void assign(PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { - reference = pointer; -} - -template -static bool valid(const PointerReferenceWrapper& reference) { - return reference.get().operator bool(); -} - -template -static inline void reset(PointerReferenceWrapper& reference) { - return reference.clear(); -} - -using BufferReference = PointerReferenceWrapper; -using TextureReference = PointerReferenceWrapper; -using FramebufferReference = PointerReferenceWrapper; -using FormatReference = PointerReferenceWrapper; -using PipelineReference = PointerReferenceWrapper; - -#define GPU_REFERENCE_INIT_VALUE - -#elif defined(GPU_POINTER_STORAGE_RAW) - -template -static bool compare(const T* const& rawPointer, const std::shared_ptr& pointer) { - return rawPointer == pointer.get(); -} - -template -static inline T* acquire(T*& rawPointer) { - return rawPointer; -} - -template -static inline bool valid(const T* const& rawPointer) { - return rawPointer; -} - -template -static inline void reset(T*& rawPointer) { - rawPointer = nullptr; -} - -template -static inline void assign(T*& rawPointer, const std::shared_ptr& pointer) { - rawPointer = pointer.get(); -} - -using BufferReference = Buffer*; -using TextureReference = Texture*; -using FramebufferReference = Framebuffer*; -using FormatReference = Stream::Format*; -using PipelineReference = Pipeline*; - -#define GPU_REFERENCE_INIT_VALUE nullptr - -#endif - class GLBackend : public Backend, public std::enable_shared_from_this { // Context Backend static interface required friend class gpu::Context; @@ -583,13 +438,13 @@ protected: BufferState& operator=(const BufferState& other) = delete; void reset() { - gpu::gl::reset(buffer); + gpu::reset(buffer); offset = 0; size = 0; } bool compare(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) { const auto& self = *this; - return (self.offset == offset && self.size == size && gpu::gl::compare(self.buffer, buffer)); + return (self.offset == offset && self.size == size && gpu::compare(self.buffer, buffer)); } }; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index 411e16b18e..a10d6c96c4 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -73,7 +73,7 @@ void GLBackend::do_advance(const Batch& batch, size_t paramOffset) { } void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { - if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) { + if (_stereo.isStereo() && !_pipeline._stateCache.flags.scissorEnable) { qWarning("Clear without scissor in stereo mode"); } @@ -140,7 +140,7 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { } // Apply scissor if needed and if not already on - bool doEnableScissor = (useScissor && (!_pipeline._stateCache.scissorEnable)); + bool doEnableScissor = (useScissor && (!_pipeline._stateCache.flags.scissorEnable)); if (doEnableScissor) { glEnable(GL_SCISSOR_TEST); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp index 8363af9b00..5da32f39df 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp @@ -100,17 +100,17 @@ void GLBackend::do_setStateCullMode(int32 mode) { } void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { - if (_pipeline._stateCache.frontFaceClockwise != isClockwise) { + if (_pipeline._stateCache.flags.frontFaceClockwise != isClockwise) { static GLenum GL_FRONT_FACES[] = { GL_CCW, GL_CW }; glFrontFace(GL_FRONT_FACES[isClockwise]); (void)CHECK_GL_ERROR(); - _pipeline._stateCache.frontFaceClockwise = isClockwise; + _pipeline._stateCache.flags.frontFaceClockwise = isClockwise; } } void GLBackend::do_setStateDepthClampEnable(bool enable) { - if (_pipeline._stateCache.depthClampEnable != enable) { + if (_pipeline._stateCache.flags.depthClampEnable != enable) { #if !defined(USE_GLES) if (enable) { glEnable(GL_DEPTH_CLAMP); @@ -118,13 +118,13 @@ void GLBackend::do_setStateDepthClampEnable(bool enable) { glDisable(GL_DEPTH_CLAMP); } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.depthClampEnable = enable; + _pipeline._stateCache.flags.depthClampEnable = enable; #endif } } void GLBackend::do_setStateScissorEnable(bool enable) { - if (_pipeline._stateCache.scissorEnable != enable) { + if (_pipeline._stateCache.flags.scissorEnable != enable) { if (enable) { glEnable(GL_SCISSOR_TEST); } else { @@ -132,12 +132,12 @@ void GLBackend::do_setStateScissorEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.scissorEnable = enable; + _pipeline._stateCache.flags.scissorEnable = enable; } } void GLBackend::do_setStateMultisampleEnable(bool enable) { - if (_pipeline._stateCache.multisampleEnable != enable) { + if (_pipeline._stateCache.flags.multisampleEnable != enable) { #if !defined(USE_GLES) if (enable) { glEnable(GL_MULTISAMPLE); @@ -146,13 +146,13 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.multisampleEnable = enable; + _pipeline._stateCache.flags.multisampleEnable = enable; #endif } } void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { - if (_pipeline._stateCache.antialisedLineEnable != enable) { + if (_pipeline._stateCache.flags.antialisedLineEnable != enable) { #if !defined(USE_GLES) if (enable) { glEnable(GL_LINE_SMOOTH); @@ -161,7 +161,7 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.antialisedLineEnable = enable; + _pipeline._stateCache.flags.antialisedLineEnable = enable; #endif } } @@ -206,7 +206,7 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { if (CHECK_GL_ERROR()) { qCDebug(gpulogging) << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") - << "Func=" << test.getFunction() + << "Func=" << (uint16_t)test.getFunction() << "Raw=" << test.getRaw(); } _pipeline._stateCache.depthTest = test; @@ -264,7 +264,7 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S } void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { - if (_pipeline._stateCache.alphaToCoverageEnable != enable) { + if (_pipeline._stateCache.flags.alphaToCoverageEnable != enable) { if (enable) { glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); } else { @@ -272,7 +272,7 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.alphaToCoverageEnable = enable; + _pipeline._stateCache.flags.alphaToCoverageEnable = enable; } } @@ -317,7 +317,7 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { mask & State::ColorMask::WRITE_ALPHA); (void)CHECK_GL_ERROR(); - _pipeline._stateCache.colorWriteMask = mask; + _pipeline._stateCache.colorWriteMask = (State::ColorMask)mask; } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp index 9ba85b8418..995feaa7a4 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp @@ -174,17 +174,17 @@ void getCurrentGLState(State::Data& state) { { GLint winding; glGetIntegerv(GL_FRONT_FACE, &winding); - state.frontFaceClockwise = (winding == GL_CW); + state.flags.frontFaceClockwise = (winding == GL_CW); #if defined(USE_GLES) - state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE_EXT); - state.antialisedLineEnable = false; - state.depthClampEnable = false; + state.flags.multisampleEnable = glIsEnabled(GL_MULTISAMPLE_EXT); + state.flags.antialisedLineEnable = false; + state.flags.depthClampEnable = false; #else - state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); - state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); - state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); + state.flags.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); + state.flags.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); + state.flags.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); #endif - state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); + state.flags.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); } { if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { @@ -247,7 +247,7 @@ void getCurrentGLState(State::Data& state) { state.sampleMask = mask; } { - state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); + state.flags.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); } { GLboolean isEnabled = glIsEnabled(GL_BLEND); @@ -272,10 +272,10 @@ void getCurrentGLState(State::Data& state) { { GLboolean mask[4]; glGetBooleanv(GL_COLOR_WRITEMASK, mask); - state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) + state.colorWriteMask = (State::ColorMask)((mask[0] ? State::WRITE_RED : 0) | (mask[1] ? State::WRITE_GREEN : 0) | (mask[2] ? State::WRITE_BLUE : 0) - | (mask[3] ? State::WRITE_ALPHA : 0); + | (mask[3] ? State::WRITE_ALPHA : 0)); } (void)CHECK_GL_ERROR(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp index b6d917b928..89ba80031b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp @@ -56,11 +56,11 @@ const GLState::Commands makeResetStateCommands() { return { std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), - std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), - std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), - std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), - std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), - std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), + std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.flags.frontFaceClockwise), + std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.flags.depthClampEnable), + std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.flags.scissorEnable), + std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.flags.multisampleEnable), + std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.flags.antialisedLineEnable), // Depth bias has 2 fields in State but really one call in GLBackend CommandPointer(depthBiasCommand), @@ -75,7 +75,7 @@ const GLState::Commands makeResetStateCommands() { std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), + std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.flags.alphaToCoverageEnable), std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 7573cc5473..86a307fe17 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -4,3 +4,4 @@ setup_hifi_library() link_hifi_libraries(shared ktx shaders) target_nsight() +target_json() diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index cd7c7c881e..e6217cc600 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -45,7 +45,7 @@ size_t Batch::_dataMax{ BATCH_PREALLOCATE_MIN }; size_t Batch::_objectsMax{ BATCH_PREALLOCATE_MIN }; size_t Batch::_drawCallInfosMax{ BATCH_PREALLOCATE_MIN }; -Batch::Batch(const char* name) { +Batch::Batch(const std::string& name) { _name = name; _commands.reserve(_commandsMax); _commandOffsets.reserve(_commandOffsetsMax); @@ -64,7 +64,7 @@ Batch::~Batch() { _drawCallInfosMax = std::max(_drawCallInfos.size(), _drawCallInfosMax); } -void Batch::setName(const char* name) { +void Batch::setName(const std::string& name) { _name = name; } @@ -96,7 +96,7 @@ void Batch::clear() { _textureTables.clear(); _transforms.clear(); - _name = nullptr; + _name.clear(); _invalidModel = true; _currentModel = Transform(); _drawcallUniform = 0; diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 96a45d3111..0a438ea148 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -89,14 +89,14 @@ public: void captureDrawCallInfo(); void captureNamedDrawCallInfo(std::string name); - Batch(const char* name = nullptr); + Batch(const std::string& name = ""); // Disallow copy construction and assignement of batches Batch(const Batch& batch) = delete; Batch& operator=(const Batch& batch) = delete; ~Batch(); - void setName(const char* name); - const char* getName() const { return _name; } + void setName(const std::string& name); + const std::string& getName() const { return _name; } void clear(); // Batches may need to override the context level stereo settings @@ -440,6 +440,18 @@ public: }; }; + using CommandHandler = std::function; + + void forEachCommand(const CommandHandler& handler) const { + size_t count = _commands.size(); + for (size_t i = 0; i < count; ++i) { + const auto command = _commands[i]; + const auto offset = _commandOffsets[i]; + const Param* params = _params.data() + offset; + handler(command, params); + } + } + typedef Cache::Vector BufferCaches; typedef Cache::Vector TextureCaches; typedef Cache::Vector TextureTableCaches; @@ -519,7 +531,7 @@ public: bool _enableSkybox { false }; protected: - const char* _name; + std::string _name; friend class Context; friend class Frame; diff --git a/libraries/gpu/src/gpu/Buffer.h b/libraries/gpu/src/gpu/Buffer.h index 01cc652fd1..49eed80b54 100644 --- a/libraries/gpu/src/gpu/Buffer.h +++ b/libraries/gpu/src/gpu/Buffer.h @@ -149,7 +149,8 @@ protected: Size _end{ 0 }; Sysmem _sysmem; - + friend class Serializer; + friend class Deserializer; friend class BufferView; friend class Frame; friend class Batch; diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 610c3d1308..45ee4263a3 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -47,10 +47,7 @@ Context::Context(const Context& context) { } Context::~Context() { - for (auto batch : _batchPool) { - delete batch; - } - _batchPool.clear(); + clearBatches(); _syncedPrograms.clear(); } @@ -97,6 +94,12 @@ FramePointer Context::endFrame() { return result; } +void Context::executeBatch(const char* name, std::function lambda) const { + auto batch = acquireBatch(name); + lambda(*batch); + executeBatch(*batch); +} + void Context::executeBatch(Batch& batch) const { PROFILE_RANGE(render_gpu, __FUNCTION__); batch.flush(); @@ -117,28 +120,27 @@ void Context::executeFrame(const FramePointer& frame) const { PROFILE_RANGE(render_gpu, __FUNCTION__); // Grab the stats at the around the frame and delta to have a consistent sampling - ContextStats beginStats; + static ContextStats beginStats; getStats(beginStats); // FIXME? probably not necessary, but safe consumeFrameUpdates(frame); _backend->setStereoState(frame->stereoState); - { - Batch beginBatch("Context::executeFrame::begin"); - _frameRangeTimer->begin(beginBatch); - _backend->render(beginBatch); - // Execute the frame rendering commands - for (auto& batch : frame->batches) { - _backend->render(*batch); - } - - Batch endBatch("Context::executeFrame::end"); - _frameRangeTimer->end(endBatch); - _backend->render(endBatch); + executeBatch("Context::executeFrame::begin", [&](Batch& batch){ + batch.pushProfileRange("Frame"); + _frameRangeTimer->begin(batch); + }); + // Execute the frame rendering commands + for (auto& batch : frame->batches) { + _backend->render(*batch); } + executeBatch("Context::executeFrame::end", [&](Batch& batch){ + batch.popProfileRange(); + _frameRangeTimer->end(batch); + }); - ContextStats endStats; + static ContextStats endStats; getStats(endStats); _frameStats.evalDelta(beginStats, endStats); } @@ -381,6 +383,16 @@ void Context::processProgramsToSync() { } } +std::mutex Context::_batchPoolMutex; +std::list Context::_batchPool; + +void Context::clearBatches() { + for (auto batch : _batchPool) { + delete batch; + } + _batchPool.clear(); +} + BatchPointer Context::acquireBatch(const char* name) { Batch* rawBatch = nullptr; { @@ -393,8 +405,10 @@ BatchPointer Context::acquireBatch(const char* name) { if (!rawBatch) { rawBatch = new Batch(); } - rawBatch->setName(name); - return BatchPointer(rawBatch, [this](Batch* batch) { releaseBatch(batch); }); + if (name) { + rawBatch->setName(name); + } + return BatchPointer(rawBatch, [](Batch* batch) { releaseBatch(batch); }); } void Context::releaseBatch(Batch* batch) { @@ -406,7 +420,7 @@ void Context::releaseBatch(Batch* batch) { void gpu::doInBatch(const char* name, const std::shared_ptr& context, const std::function& f) { - auto batch = context->acquireBatch(name); + auto batch = Context::acquireBatch(name); f(*batch); context->appendFrameBatch(batch); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 0626ecd8f3..b080b0ceac 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -24,6 +24,7 @@ #include "Pipeline.h" #include "Framebuffer.h" #include "Frame.h" +#include "PointerStorage.h" class QImage; @@ -162,8 +163,8 @@ public: void appendFrameBatch(const BatchPointer& batch); FramePointer endFrame(); - BatchPointer acquireBatch(const char* name = nullptr); - void releaseBatch(Batch* batch); + static BatchPointer acquireBatch(const char* name = nullptr); + static void releaseBatch(Batch* batch); // MUST only be called on the rendering thread // @@ -175,6 +176,11 @@ public: // Execute a batch immediately, rather than as part of a frame void executeBatch(Batch& batch) const; + // MUST only be called on the rendering thread + // + // Execute a batch immediately, rather than as part of a frame + void executeBatch(const char* name, std::function lambda) const; + // MUST only be called on the rendering thread // // Executes a frame, applying any updates contained in the frame batches to the rendering @@ -267,8 +273,6 @@ protected: Context(const Context& context); std::shared_ptr _backend; - std::mutex _batchPoolMutex; - std::list _batchPool; bool _frameActive{ false }; FramePointer _currentFrame; RangeTimerPointer _frameRangeTimer; @@ -285,6 +289,11 @@ protected: static CreateBackend _createBackendCallback; static std::once_flag _initialized; + // Should probably move this functionality to Batch + static void clearBatches(); + static std::mutex _batchPoolMutex; + static std::list _batchPool; + friend class Shader; friend class Backend; }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 7a3e0a2f82..705d746a5b 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -322,7 +322,7 @@ public: uint8 getLocationScalarCount() const { return DIMENSION_SCALAR_COUNT_PER_LOCATION[(Dimension)_dimension]; } uint32 getLocationSize() const { return DIMENSION_SCALAR_COUNT_PER_LOCATION[_dimension] * TYPE_SIZE[_type]; } - uint16 getRaw() const { return *((uint16*) (this)); } + uint16 getRaw() const { return *((const uint16*) (this)); } bool operator ==(const Element& right) const { @@ -376,7 +376,7 @@ public: }; -enum ComparisonFunction { +enum ComparisonFunction : uint16 { NEVER = 0, LESS, EQUAL, diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 0592b71889..7e03816116 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -96,6 +96,7 @@ namespace gpu { using TextureTablePointer = std::shared_ptr; struct StereoState { + StereoState() {} bool isStereo() const { return _enable && !_contextDisable; } @@ -108,6 +109,9 @@ namespace gpu { Mat4 _eyeProjections[2]; }; + class Serializer; + class Deserializer; + class GPUObject { public: virtual ~GPUObject() = default; diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index a3a5d3489c..3787ebfacd 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -42,6 +42,8 @@ namespace gpu { FramebufferRecycler framebufferRecycler; protected: + friend class Deserializer; + // Should be called once per frame, on the recording thred void finish(); void preRender(); diff --git a/libraries/gpu/src/gpu/FrameIO.h b/libraries/gpu/src/gpu/FrameIO.h new file mode 100644 index 0000000000..4de4cf5fe7 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameIO.h @@ -0,0 +1,29 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// Copyright 2013-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 +// +#pragma once +#ifndef hifi_gpu_FrameIO_h +#define hifi_gpu_FrameIO_h + +#include "Forward.h" +#include "Format.h" + +#include + +namespace gpu { + +using TextureCapturer = std::function; +using TextureLoader = std::function; +void writeFrame(const std::string& filename, const FramePointer& frame, const TextureCapturer& capturer = nullptr); +FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader = nullptr); + +using IndexOptimizer = std::function; +void optimizeFrame(const std::string& filename, const IndexOptimizer& optimizer); + +} // namespace gpu + +#endif diff --git a/libraries/gpu/src/gpu/FrameIOKeys.h b/libraries/gpu/src/gpu/FrameIOKeys.h new file mode 100644 index 0000000000..5d5f8a5bd9 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameIOKeys.h @@ -0,0 +1,214 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// Copyright 2013-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 +// +#pragma once +#ifndef hifi_gpu_FrameIOKeys_h +#define hifi_gpu_FrameIOKeys_h + +namespace gpu { namespace keys { + +static const char* binary = "binary"; +static const char* L00 = "L00"; +static const char* L1m1 = "L1m1"; +static const char* L10 = "L10"; +static const char* L11 = "L11"; +static const char* L2m2 = "L2m2"; +static const char* L2m1 = "L2m1"; +static const char* L20 = "L20"; +static const char* L21 = "L21"; +static const char* L22 = "L22"; + +static const char* eyeProjections = "eyeProjections"; +static const char* eyeViews = "eyeViews"; +static const char* alphaToCoverageEnable = "alphaToCoverageEnable"; +static const char* antialisedLineEnable = "antialisedLineEnable"; +static const char* attributes = "attributes"; +static const char* batches = "batches"; +static const char* blendFunction = "blendFunction"; +static const char* borderColor = "borderColor"; +static const char* bufferMask = "bufferMask"; +static const char* buffers = "buffers"; +static const char* capturedTextures = "capturedTextures"; +static const char* channel = "channel"; +static const char* colorAttachments = "colorAttachments"; +static const char* colorWriteMask = "colorWriteMask"; +static const char* commands = "commands"; +static const char* comparisonFunction = "comparisonFunction"; +static const char* cullMode = "cullMode"; +static const char* data = "data"; +static const char* depth = "depth"; +static const char* depthBias = "depthBias"; +static const char* depthBiasSlopeScale = "depthBiasSlopeScale"; +static const char* depthClampEnable = "depthClampEnable"; +static const char* depthStencilAttachment = "depthStencilAttachment"; +static const char* depthTest = "depthTest"; +static const char* drawCallInfos = "drawCallInfos"; +static const char* drawcallUniform = "drawcallUniform"; +static const char* drawcallUniformReset = "drawcallUniformReset"; +static const char* element = "element"; +static const char* fillMode = "fillMode"; +static const char* filter = "filter"; +static const char* formats = "formats"; +static const char* frameIndex = "frameIndex"; +static const char* framebuffer = "framebuffer"; +static const char* framebuffers = "framebuffers"; +static const char* frequency = "frequency"; +static const char* frontFaceClockwise = "frontFaceClockwise"; +static const char* height = "height"; +static const char* id = "id"; +static const char* ktxFile = "ktxFile"; +static const char* layers = "layers"; +static const char* maxAnisotropy = "maxAnisotropy"; +static const char* maxMip = "maxMip"; +static const char* minMip = "minMip"; +static const char* mipOffset = "mipOffset"; +static const char* mips = "mips"; +static const char* multisampleEnable = "multisampleEnable"; +static const char* name = "name"; +static const char* namedData = "namedData"; +static const char* names = "names"; +static const char* objects = "objects"; +static const char* offset = "offset"; +static const char* pipelines = "pipelines"; +static const char* pose = "pose"; +static const char* profileRanges = "profileRanges"; +static const char* program = "program"; +static const char* programs = "programs"; +static const char* projectionJitter = "projectionJitter"; +static const char* queries = "queries"; +static const char* sampleCount = "sampleCount"; +static const char* sampleMask = "sampleMask"; +static const char* sampler = "sampler"; +static const char* samples = "samples"; +static const char* scissorEnable = "scissorEnable"; +static const char* shaders = "shaders"; +static const char* size = "size"; +static const char* skybox = "skybox"; +static const char* slot = "slot"; +static const char* source = "source"; +static const char* state = "state"; +static const char* stencilActivation = "stencilActivation"; +static const char* stencilTestBack = "stencilTestBack"; +static const char* stencilTestFront = "stencilTestFront"; +static const char* stereo = "stereo"; +static const char* subresource = "subresource"; +static const char* swapchains = "swapchains"; +static const char* texelFormat = "texelFormat"; +static const char* texture = "texture"; +static const char* textureTables = "textureTables"; +static const char* textures = "textures"; +static const char* transforms = "transforms"; +static const char* type = "type"; +static const char* usageType = "usageType"; +static const char* view = "view"; +static const char* width = "width"; +static const char* wrapModeU = "wrapModeU"; +static const char* wrapModeV = "wrapModeV"; +static const char* wrapModeW = "wrapModeW"; + + +static const char* backWriteMask = "backWriteMask"; +static const char* frontWriteMask = "frontWriteMask"; +static const char* reference = "reference"; +static const char* readMask = "readMask"; +static const char* failOp = "failOp"; +static const char* depthFailOp = "depthFailOp"; +static const char* passOp = "passOp"; +static const char* enabled = "enabled"; +static const char* blend = "blend"; +static const char* flags = "flags"; +static const char* writeMask = "writeMask"; +static const char* function = "function"; +static const char* sourceColor = "sourceColor"; +static const char* sourceAlpha = "sourceAlpha"; +static const char* destColor = "destColor"; +static const char* destAlpha = "destAlpha"; +static const char* opColor = "opColor"; +static const char* opAlpha = "opAlpha"; +static const char* enable = "enable"; +static const char* contextDisable = "contextDisable"; + +static const char* COMMAND_NAMES[] = { + "draw", + "drawIndexed", + "drawInstanced", + "drawIndexedInstanced", + "multiDrawIndirect", + "multiDrawIndexedIndirect", + + "setInputFormat", + "setInputBuffer", + "setIndexBuffer", + "setIndirectBuffer", + + "setModelTransform", + "setViewTransform", + "setProjectionTransform", + "setProjectionJitter", + "setViewportTransform", + "setDepthRangeTransform", + + "setPipeline", + "setStateBlendFactor", + "setStateScissorRect", + + "setUniformBuffer", + "setResourceBuffer", + "setResourceTexture", + "setResourceTextureTable", + "setResourceFramebufferSwapChainTexture", + + "setFramebuffer", + "setFramebufferSwapChain", + "clearFramebuffer", + "blit", + "generateTextureMips", + "generateTextureMipsWithPipeline", + + "advance", + + "beginQuery", + "endQuery", + "getQuery", + + "resetStages", + + "disableContextViewCorrection", + "restoreContextViewCorrection", + + "disableContextStereo", + "restoreContextStereo", + + "runLambda", + + "startNamedCall", + "stopNamedCall", + + "glUniform1i", + "glUniform1f", + "glUniform2f", + "glUniform3f", + "glUniform4f", + "glUniform3fv", + "glUniform4fv", + "glUniform4iv", + "glUniformMatrix3fv", + "glUniformMatrix4fv", + + "glColor4f", + + "pushProfileRange", + "popProfileRange", +}; + +template +constexpr size_t array_size(T (&)[N]) { return N; } + +static_assert(array_size(COMMAND_NAMES) == Batch::Command::NUM_COMMANDS, "Command array sizes must match"); +}} // namespace gpu::keys + +#endif diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp new file mode 100644 index 0000000000..c63eaf51c3 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -0,0 +1,906 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "FrameIO.h" + +#include +#include + +#include +#include + +#include +#include "Frame.h" +#include "Batch.h" +#include "TextureTable.h" + + +#include "FrameIOKeys.h" + +namespace gpu { +using json = nlohmann::json; + +class Deserializer { +public: + static std::string getBaseName(const std::string& filename) { + static const std::string ext{ ".json" }; + if (std::string::npos != filename.rfind(ext)) { + return filename.substr(0, filename.size() - ext.size()); + } + return filename; + } + + Deserializer(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) : + basename(getBaseName(filename)), externalTexture(externalTexture), textureLoader(loader) {} + + const std::string basename; + std::string basedir; + std::string binaryFile; + const uint32_t externalTexture; + TextureLoader textureLoader; + std::vector shaders; + std::vector programs; + std::vector textures; + std::vector textureTables; + std::vector buffers; + std::unordered_map bufferOffsets; + std::vector formats; + std::vector pipelines; + std::vector framebuffers; + std::vector swapchains; + std::vector queries; + json frameNode; + FramePointer readFrame(); + void optimizeFrame(const IndexOptimizer& optimizer); + + FramePointer deserializeFrame(); + + + void readBuffers(const json& node); + + template + static std::vector readArray(const json& node, const std::string& name, std::function parser) { + std::vector result; + if (node.count(name)) { + const auto& sourceArrayNode = node[name]; + result.reserve(sourceArrayNode.size()); + for (const auto& sourceNode : sourceArrayNode) { + if (sourceNode.is_null()) { + result.push_back(nullptr); + continue; + } + result.push_back(parser(sourceNode)); + } + } + return result; + } + + template + static std::vector readNumericVector(const json& node) { + auto count = node.size(); + std::vector result; + result.resize(count); + for (size_t i = 0; i < count; ++i) { + result[i] = node[i]; + } + return result; + } + + template + static void readFloatArray(const json& node, float* out) { + for (size_t i = 0; i < N; ++i) { + out[i] = node[i].operator float(); + } + } + + template + static bool readOptionalTransformed(T& dest, const json& node, const std::string& name, std::function f) { + if (node.count(name)) { + dest = f(node[name]); + return true; + } + return false; + } + + template + static bool readOptionalVectorTransformed(std::vector& dest, + const json& node, + const std::string& name, + std::function f) { + if (node.count(name)) { + const auto& arrayNode = node[name]; + const auto count = arrayNode.size(); + dest.reserve(count); + for (size_t i = 0; i < count; ++i) { + dest.emplace_back(f(arrayNode[i])); + } + return true; + } + return false; + } + + template + static bool readOptionalVector(std::vector& dest, const json& node, const std::string& name) { + return readOptionalVectorTransformed(dest, node, name, [](const json& node) { return node.get(); }); + } + + template + static T defaultNodeTransform(const json& node) { + return node.get(); + } + + template + static bool readBatchCacheTransformed(typename Batch::Cache::Vector& dest, + const json& node, + const std::string& name, + std::function f = [](const json& node) -> TT { + return node.get(); + }) { + if (node.count(name)) { + const auto& arrayNode = node[name]; + for (const auto& entry : arrayNode) { + dest.cache(f(entry)); + } + return true; + } + return false; + } + + template + static bool readPointerCache(typename Batch::Cache::Vector& dest, + const json& node, + const std::string& name, + std::vector& global) { + auto transform = [&](const json& node) -> const T& { return global[node.get()]; }; + return readBatchCacheTransformed(dest, node, name, transform); + } + + template + static bool readOptional(T& dest, const json& node, const std::string& name) { + return readOptionalTransformed(dest, node, name, [](const json& child) { + T result = child; + return result; + }); + } + + SwapChainPointer readSwapchain(const json& node); + ShaderPointer readProgram(const json& node); + PipelinePointer readPipeline(const json& node); + TextureTablePointer readTextureTable(const json& node); + TextureView readTextureView(const json& node); + FramebufferPointer readFramebuffer(const json& node); + BatchPointer readBatch(const json& node); + Batch::NamedBatchData readNamedBatchData(const json& node); + + //static StatePointer readState(const json& node); + static QueryPointer readQuery(const json& node); + TexturePointer readTexture(const json& node, uint32_t externalTexture); + static ShaderPointer readShader(const json& node); + static Stream::FormatPointer readFormat(const json& node); + static Element readElement(const json& node); + static Sampler readSampler(const json& node); + static glm::mat4 readMat4(const json& node) { + glm::mat4 m; + if (!node.is_null()) { + readFloatArray<16>(node, &m[0][0]); + } + return m; + } + static glm::vec4 readVec4(const json& node) { + glm::vec4 v; + if (!node.is_null()) { + readFloatArray<4>(node, &v[0]); + } + return v; + } + static glm::vec3 readVec3(const json& node) { + glm::vec3 v; + if (!node.is_null()) { + readFloatArray<3>(node, &v[0]); + } + return v; + } + static glm::vec2 readVec2(const json& node) { + glm::vec2 v; + if (!node.is_null()) { + readFloatArray<2>(node, &v[0]); + } + return v; + } + static Transform readTransform(const json& node) { return Transform{ readMat4(node) }; } + static std::vector fromBase64(const json& node); + static void readCommand(const json& node, Batch& batch); +}; + +FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) { + return Deserializer(filename, externalTexture, loader).readFrame(); +} + +void optimizeFrame(const std::string& filename, const IndexOptimizer& optimizer) { + return Deserializer(filename, 0, {}).optimizeFrame(optimizer); +} + +} // namespace gpu + +using namespace gpu; + +void Deserializer::readBuffers(const json& buffersNode) { + storage::FileStorage mappedFile(binaryFile.c_str()); + const auto mappedSize = mappedFile.size(); + const auto* mapped = mappedFile.data(); + size_t bufferCount = buffersNode.size(); + buffers.reserve(buffersNode.size()); + size_t offset = 0; + for (size_t i = 0; i < bufferCount; ++i) { + const auto& bufferNode = buffersNode[i]; + if (bufferNode.is_null()) { + buffers.push_back(nullptr); + continue; + } + + size_t size = bufferNode; + if (offset + size > mappedSize) { + throw std::runtime_error("read buffer error"); + } + buffers.push_back(std::make_shared(size, mapped + offset)); + bufferOffsets[buffers.back()] = offset; + offset += size; + } +} + +Element Deserializer::readElement(const json& node) { + Element result; + if (!node.is_null()) { + *((uint16*)&result) = node; + } + return result; +} + +Sampler Deserializer::readSampler(const json& node) { + Sampler result; + if (!node.is_null()) { + if (node.count(keys::borderColor)) { + result._desc._borderColor = readVec4(node[keys::borderColor]); + } + if (node.count(keys::maxAnisotropy)) { + result._desc._maxAnisotropy = node[keys::maxAnisotropy]; + } + if (node.count(keys::wrapModeU)) { + result._desc._wrapModeU = node[keys::wrapModeU]; + } + if (node.count(keys::wrapModeV)) { + result._desc._wrapModeV = node[keys::wrapModeV]; + } + if (node.count(keys::wrapModeW)) { + result._desc._wrapModeW = node[keys::wrapModeW]; + } + if (node.count(keys::filter)) { + result._desc._filter = node[keys::filter]; + } + if (node.count(keys::comparisonFunction)) { + result._desc._comparisonFunc = node[keys::comparisonFunction]; + } + if (node.count(keys::minMip)) { + result._desc._minMip = node[keys::minMip]; + } + if (node.count(keys::maxMip)) { + result._desc._maxMip = node[keys::maxMip]; + } + if (node.count(keys::mipOffset)) { + result._desc._mipOffset = node[keys::mipOffset]; + } + } + return result; +} + +TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { + if (node.is_null()) { + return nullptr; + } + + TextureUsageType usageType = node[keys::usageType]; + Texture::Type type = node[keys::type]; + glm::u16vec4 dims; + dims.x = node[keys::width]; + dims.y = node[keys::height]; + dims.z = node[keys::depth]; + dims.w = node[keys::layers]; + uint16 mips = node[keys::mips]; + uint16 samples = node[keys::samples]; + Element texelFormat = readElement(node[keys::texelFormat]); + Sampler sampler; + readOptionalTransformed(sampler, node, keys::sampler, [](const json& node) { return readSampler(node); }); + TexturePointer result; + if (usageType == TextureUsageType::EXTERNAL) { + result = Texture::createExternal([](uint32_t, void*) {}); + result->setExternalTexture(external, nullptr); + } else { + result = Texture::create(usageType, type, texelFormat, dims.x, dims.y, dims.z, samples, dims.w, mips, sampler); + } + + auto& texture = *result; + readOptional(texture._source, node, keys::source); + + std::string ktxFile; + readOptional(ktxFile, node, keys::ktxFile); + if (!ktxFile.empty()) { + if (QFileInfo(ktxFile.c_str()).isRelative()) { + ktxFile = basedir + "/" + ktxFile; + } + texture.setKtxBacking(ktxFile); + } + return result; +} + +SwapChainPointer Deserializer::readSwapchain(const json& node) { + if (node.is_null()) { + return nullptr; + } + + uint8_t swapChainSize = node[keys::size]; + std::vector swapChainFramebuffers; + const auto& framebuffersNode = node[keys::framebuffers]; + swapChainFramebuffers.resize(swapChainSize); + for (uint8_t i = 0; i < swapChainSize; ++i) { + auto index = framebuffersNode[i].get(); + swapChainFramebuffers[i] = framebuffers[index]; + } + return std::make_shared(swapChainFramebuffers); +} + +ShaderPointer Deserializer::readShader(const json& node) { + if (node.is_null()) { + return nullptr; + } + + // FIXME support procedural shaders + Shader::Type type = node[keys::type]; + uint32_t id = node[keys::id]; + ShaderPointer result; + switch (type) { + //case Shader::Type::GEOMETRY: + // result = Shader::createGeometry(id); + // break; + case Shader::Type::VERTEX: + result = Shader::createVertex(id); + break; + case Shader::Type::FRAGMENT: + result = Shader::createPixel(id); + break; + default: + throw std::runtime_error("not implemented"); + } + return result; +} + +ShaderPointer Deserializer::readProgram(const json& node) { + if (node.is_null()) { + return nullptr; + } + + std::vector programShaders; + programShaders.reserve(node.size()); + for (const auto& shaderRef : node) { + uint32_t shaderIndex = shaderRef; + programShaders.push_back(this->shaders[shaderIndex]); + } + + // FIXME account for geometry and compute shaders? + return Shader::createProgram(programShaders[0], programShaders[1]); +} + +static State::Flags readStateFlags(const json& node) { + State::Flags result; + // Hacky implementation because you can't pass boolean bitfields as references + bool value; + if (Deserializer::readOptional(value, node, keys::alphaToCoverageEnable)) { + result.alphaToCoverageEnable = value; + } + if (Deserializer::readOptional(value, node, keys::frontFaceClockwise)) { + result.frontFaceClockwise = value; + } + if (Deserializer::readOptional(value, node, keys::depthClampEnable)) { + result.depthClampEnable = value; + } + if (Deserializer::readOptional(value, node, keys::scissorEnable)) { + result.scissorEnable = value; + } + if (Deserializer::readOptional(value, node, keys::multisampleEnable)) { + result.multisampleEnable = value; + } + if (Deserializer::readOptional(value, node, keys::antialisedLineEnable)) { + result.antialisedLineEnable = value; + } + if (Deserializer::readOptional(value, node, keys::alphaToCoverageEnable)) { + result.alphaToCoverageEnable = value; + } + return result; +} + +static State::BlendFunction readBlendFunction(const json& node) { + State::BlendFunction result; + uint16 enabled; + State::BlendArg blendArg; + State::BlendOp blendOp; + if (Deserializer::readOptional(enabled, node, keys::enabled)) { + result.enabled = enabled; + } + if (Deserializer::readOptional(blendArg, node, keys::sourceColor)) { + result.sourceColor = blendArg; + } + if (Deserializer::readOptional(blendArg, node, keys::sourceAlpha)) { + result.sourceAlpha = blendArg; + } + if (Deserializer::readOptional(blendArg, node, keys::destColor)) { + result.destColor = blendArg; + } + if (Deserializer::readOptional(blendArg, node, keys::destAlpha)) { + result.destAlpha = blendArg; + } + if (Deserializer::readOptional(blendOp, node, keys::opAlpha)) { + result.opAlpha = blendOp; + } + if (Deserializer::readOptional(blendOp, node, keys::opColor)) { + result.opColor = blendOp; + } + return result; +} + +static State::DepthTest readDepthTest(const json& node) { + State::DepthTest result; + Deserializer::readOptional(result.writeMask, node, keys::writeMask); + Deserializer::readOptional(result.enabled, node, keys::enabled); + Deserializer::readOptional(result.function, node, keys::function); + return result; +} + +static State::StencilTest readStencilTest(const json& node) { + State::StencilTest result; + State::ComparisonFunction compareOp; + State::StencilOp stencilOp; + if (Deserializer::readOptional(compareOp, node, keys::function)) { + result.function = compareOp; + } + if (Deserializer::readOptional(stencilOp, node, keys::failOp)) { + result.failOp = stencilOp; + } + if (Deserializer::readOptional(stencilOp, node, keys::depthFailOp)) { + result.depthFailOp = stencilOp; + } + if (Deserializer::readOptional(stencilOp, node, keys::passOp)) { + result.passOp = stencilOp; + } + if (Deserializer::readOptional(compareOp, node, keys::function)) { + result.function = compareOp; + } + Deserializer::readOptional(result.reference, node, keys::reference); + Deserializer::readOptional(result.readMask, node, keys::readMask); + return result; +} + +static State::StencilActivation readStencilActivation(const json& node) { + auto jsonString = node.dump(2); + State::StencilActivation result; + bool enabled; + if (Deserializer::readOptional(enabled, node, keys::enabled)) { + result.enabled = enabled; + } + Deserializer::readOptional(result.frontWriteMask, node, keys::frontWriteMask); + Deserializer::readOptional(result.backWriteMask, node, keys::backWriteMask); + return result; +} + +StatePointer readState(const json& node) { + if (node.is_null()) { + return nullptr; + } + + State::Data data; + Deserializer::readOptionalTransformed(data.flags, node, keys::flags, &readStateFlags); + Deserializer::readOptionalTransformed(data.blendFunction, node, keys::blendFunction, &readBlendFunction); + Deserializer::readOptionalTransformed(data.depthTest, node, keys::depthTest, &readDepthTest); + Deserializer::readOptionalTransformed(data.stencilActivation, node, keys::stencilActivation, &readStencilActivation); + Deserializer::readOptionalTransformed(data.stencilTestFront, node, keys::stencilTestFront, &readStencilTest); + Deserializer::readOptionalTransformed(data.stencilTestBack, node, keys::stencilTestBack, &readStencilTest); + Deserializer::readOptional(data.colorWriteMask, node, keys::colorWriteMask); + Deserializer::readOptional(data.cullMode, node, keys::cullMode); + Deserializer::readOptional(data.depthBias, node, keys::depthBias); + Deserializer::readOptional(data.depthBiasSlopeScale, node, keys::depthBiasSlopeScale); + Deserializer::readOptional(data.fillMode, node, keys::fillMode); + Deserializer::readOptional(data.sampleMask, node, keys::sampleMask); + return std::make_shared(data); +} + +PipelinePointer Deserializer::readPipeline(const json& node) { + if (node.is_null()) { + return nullptr; + } + + auto state = readState(node[keys::state]); + uint32_t programIndex = node[keys::program]; + auto program = programs[programIndex]; + return Pipeline::create(program, state); +} + +Stream::FormatPointer Deserializer::readFormat(const json& node) { + if (node.is_null()) { + return nullptr; + } + + auto result = std::make_shared(); + auto& format = *result; + const auto& attributesNode = node[keys::attributes]; + for (const auto& attributeNode : attributesNode) { + uint8_t slot = attributeNode[keys::slot]; + auto& attribute = format._attributes[slot]; + attribute._slot = slot; + attribute._channel = attributeNode[keys::channel]; + readOptionalTransformed(attribute._element, attributeNode, keys::element, + [](const json& node) { return readElement(node); }); + readOptional(attribute._frequency, attributeNode, keys::frequency); + readOptional(attribute._offset, attributeNode, keys::offset); + } + format.evaluateCache(); + return result; +} + +TextureTablePointer Deserializer::readTextureTable(const json& node) { + if (node.is_null()) { + return nullptr; + } + TextureTablePointer result = std::make_shared(); + auto& table = *result; + auto count = node.size(); + for (size_t i = 0; i < count; ++i) { + uint32_t index = node[i]; + table.setTexture(i, textures[index]); + } + return result; +} + +TextureView Deserializer::readTextureView(const json& node) { + TextureView result; + auto texturePointerReader = [this](const json& node) { + uint32_t textureIndex = node; + return textures[textureIndex]; + }; + readOptionalTransformed(result._texture, node, keys::texture, texturePointerReader); + readOptionalTransformed(result._element, node, keys::element, &readElement); + readOptional(result._subresource, node, keys::subresource); + return result; +} + +FramebufferPointer Deserializer::readFramebuffer(const json& node) { + if (node.is_null()) { + return nullptr; + } + + FramebufferPointer result; + { + std::string name; + readOptional(name, node, keys::name); + result.reset(Framebuffer::create(name)); + } + auto& framebuffer = *result; + readOptional(framebuffer._bufferMask, node, keys::bufferMask); + readOptional(framebuffer._height, node, keys::height); + readOptional(framebuffer._width, node, keys::width); + readOptional(framebuffer._numSamples, node, keys::sampleCount); + auto textureViewReader = [this](const json& node) -> TextureView { return readTextureView(node); }; + readOptionalTransformed(framebuffer._depthStencilBuffer, node, keys::depthStencilAttachment, + textureViewReader); + if (framebuffer._depthStencilBuffer) { + framebuffer._depthStamp++; + } + if (node.count(keys::colorAttachments)) { + const auto& colorAttachmentsNode = node[keys::colorAttachments]; + size_t count = colorAttachmentsNode.size(); + for (size_t i = 0; i < count; ++i) { + const auto& colorAttachmentNode = colorAttachmentsNode[i]; + if (colorAttachmentNode.is_null()) { + continue; + } + framebuffer._renderBuffers[i] = readTextureView(colorAttachmentNode); + framebuffer._colorStamps[i]++; + } + } + + return result; +} + +QueryPointer Deserializer::readQuery(const json& node) { + if (node.is_null()) { + return nullptr; + } + std::string name = node[keys::name]; + return std::make_shared([](const Query&) {}, name); +} + +std::vector Deserializer::fromBase64(const json& node) { + std::vector result; + auto decoded = QByteArray::fromBase64(QByteArray{ node.get().c_str() }); + result.resize(decoded.size()); + memcpy(result.data(), decoded.data(), decoded.size()); + return result; +} + +static std::unordered_map getCommandNameMap() { + static std::unordered_map result; + if (result.empty()) { + for (Batch::Command i = Batch::COMMAND_draw; i < Batch::NUM_COMMANDS; i = (Batch::Command)(i + 1)) { + result[keys::COMMAND_NAMES[i]] = i; + } + } + return result; +} + +void Deserializer::readCommand(const json& commandNode, Batch& batch) { + size_t count = commandNode.size(); + std::string commandName = commandNode[0]; + Batch::Command command = getCommandNameMap()[commandName]; + batch._commands.push_back(command); + batch._commandOffsets.push_back(batch._params.size()); + for (size_t i = 1; i < count; ++i) { + batch._params.emplace_back(commandNode[i].get()); + } +} + +Batch::NamedBatchData Deserializer::readNamedBatchData(const json& node) { + Batch::NamedBatchData result; + readOptionalVectorTransformed(result.buffers, node, keys::buffers, [this](const json& node) { + uint32_t index = node; + return buffers[index]; + }); + readOptionalVectorTransformed(result.drawCallInfos, node, keys::drawCallInfos, + [](const json& node) -> Batch::DrawCallInfo { + Batch::DrawCallInfo result{ 0 }; + *((uint32_t*)&result) = node; + return result; + }); + return result; +} + +BatchPointer Deserializer::readBatch(const json& node) { + if (node.is_null()) { + return nullptr; + } + + std::string batchName; + if (node.count(keys::name)) { + batchName = node[keys::name]; + } + BatchPointer result = std::make_shared(batchName); + auto& batch = *result; + readOptional(batch._enableStereo, node, keys::stereo); + readOptional(batch._enableSkybox, node, keys::skybox); + readOptionalTransformed(batch._projectionJitter, node, keys::projectionJitter, &readVec2); + readOptional(batch._drawcallUniform, node, keys::drawcallUniform); + readOptional(batch._drawcallUniformReset, node, keys::drawcallUniformReset); + readPointerCache(batch._textures, node, keys::textures, textures); + readPointerCache(batch._textureTables, node, keys::textureTables, textureTables); + readPointerCache(batch._buffers, node, keys::buffers, buffers); + readPointerCache(batch._pipelines, node, keys::pipelines, pipelines); + readPointerCache(batch._streamFormats, node, keys::formats, formats); + readPointerCache(batch._framebuffers, node, keys::framebuffers, framebuffers); + readPointerCache(batch._swapChains, node, keys::swapchains, swapchains); + readPointerCache(batch._queries, node, keys::queries, queries); + + readOptionalVectorTransformed(batch._drawCallInfos, node, keys::drawCallInfos, + [](const json& node) -> Batch::DrawCallInfo { + Batch::DrawCallInfo result{ 0 }; + *((uint32_t*)&result) = node; + return result; + }); + + readOptionalTransformed>(batch._data, node, keys::data, + [](const json& node) { return fromBase64(node); }); + + for (const auto& commandNode : node[keys::commands]) { + readCommand(commandNode, batch); + } + readBatchCacheTransformed(batch._transforms, node, keys::transforms, &readTransform); + readBatchCacheTransformed(batch._profileRanges, node, keys::profileRanges); + readBatchCacheTransformed(batch._names, node, keys::names); + + auto objectTransformReader = [](const json& node) -> Batch::TransformObject { + Batch::TransformObject result; + result._model = readMat4(node); + result._modelInverse = glm::inverse(result._model); + return result; + }; + readOptionalVectorTransformed(batch._objects, node, keys::objects, objectTransformReader); + + if (node.count(keys::namedData)) { + const auto& namedDataNode = node[keys::namedData]; + for (auto itr = namedDataNode.begin(); itr != namedDataNode.end(); ++itr) { + auto name = itr.key(); + batch._namedData[name] = readNamedBatchData(itr.value()); + } + } + + return result; +} + +StereoState readStereoState(const json& node) { + StereoState result; + Deserializer::readOptional(result._enable, node, keys::enable); + Deserializer::readOptional(result._contextDisable, node, keys::contextDisable); + Deserializer::readOptional(result._skybox, node, keys::skybox); + if (node.count(keys::eyeProjections)) { + auto projections = node[keys::eyeProjections]; + result._eyeProjections[0] = Deserializer::readMat4(projections[0]); + result._eyeProjections[1] = Deserializer::readMat4(projections[1]); + } + if (node.count(keys::eyeViews)) { + auto views = node[keys::eyeViews]; + result._eyeViews[0] = Deserializer::readMat4(views[0]); + result._eyeViews[1] = Deserializer::readMat4(views[1]); + } + return result; +} + + +FramePointer Deserializer::deserializeFrame() { + { + std::string filename{ basename + ".json" }; + if (0 == basename.find("assets:")) { + auto lastSlash = basename.rfind('/'); + basedir = basename.substr(0, lastSlash); + } else { + basedir = QFileInfo(basename.c_str()).absolutePath().toStdString(); + } + storage::FileStorage mappedFile(filename.c_str()); + frameNode = json::parse(std::string((const char*)mappedFile.data(), mappedFile.size())); + } + + FramePointer result = std::make_shared(); + auto& frame = *result; + + if (frameNode[keys::binary].is_string()) { + binaryFile = frameNode[keys::binary]; + if (QFileInfo(binaryFile.c_str()).isRelative()) { + binaryFile = basedir + "/" + binaryFile; + } + } else { + binaryFile = basename + ".bin"; + } + + + if (frameNode.count(keys::buffers)) { + readBuffers(frameNode[keys::buffers]); + } + + shaders = readArray(frameNode, keys::shaders, [](const json& node) { return readShader(node); }); + // Must come after shaders + programs = readArray(frameNode, keys::programs, [this](const json& node) { return readProgram(node); }); + // Must come after programs + pipelines = readArray(frameNode, keys::pipelines, [this](const json& node) { return readPipeline(node); }); + + formats = readArray(frameNode, keys::formats, [](const json& node) { return readFormat(node); }); + + auto textureReader = [this](const json& node) { return readTexture(node, externalTexture); }; + textures = readArray(frameNode, keys::textures, textureReader); + if (textureLoader) { + std::vector capturedTextures = readNumericVector(frameNode[keys::capturedTextures]); + for (const auto& index : capturedTextures) { + const auto& texturePointer = textures[index]; + uint16 layers = std::max(texturePointer->getNumSlices(), 1); + for (uint16 layer = 0; layer < layers; ++layer) { + std::string filename = basename + "." + std::to_string(index) + "." + std::to_string(layer) + ".png"; + textureLoader(filename, texturePointer, layer); + } + } + } + + // Must come after textures + auto textureTableReader = [this](const json& node) { return readTextureTable(node); }; + textureTables = readArray(frameNode, keys::textureTables, textureTableReader); + + // Must come after textures + auto framebufferReader = [this](const json& node) { return readFramebuffer(node); }; + framebuffers = readArray(frameNode, keys::framebuffers, framebufferReader); + + // Must come after textures & framebuffers + swapchains = + readArray(frameNode, keys::swapchains, [this](const json& node) { return readSwapchain(node); }); + + queries = readArray(frameNode, keys::queries, [this](const json& node) { return readQuery(node); }); + frame.framebuffer = framebuffers[frameNode[keys::framebuffer].get()]; + frame.view = readMat4(frameNode[keys::view]); + frame.pose = readMat4(frameNode[keys::pose]); + frame.frameIndex = frameNode[keys::frameIndex]; + frame.stereoState = readStereoState(frameNode[keys::stereo]); + if (frameNode.count(keys::batches)) { + for (const auto& batchNode : frameNode[keys::batches]) { + frame.batches.push_back(readBatch(batchNode)); + } + } + + return result; +} + + + +FramePointer Deserializer::readFrame() { + auto result = deserializeFrame(); + result->finish(); + return result; +} + +void Deserializer::optimizeFrame(const IndexOptimizer& optimizer) { + auto result = deserializeFrame(); + auto& frame = *result; + + + // optimize the index buffers? + struct CurrentIndexBuffer { + Offset offset{ 0 }; + BufferPointer buffer; + Type type{ gpu::Type::INT32 }; + Primitive primitve{ Primitive::TRIANGLES }; + uint32_t numIndices{ 0 }; + uint32_t startIndex{ 0 }; + }; + + std::vector captured; + for (auto& batch : frame.batches) { + + CurrentIndexBuffer currentIndexBuffer; + batch->forEachCommand([&](Batch::Command cmd, const Batch::Param* params){ + switch(cmd) { + case Batch::Command::COMMAND_setIndexBuffer: + currentIndexBuffer.offset = params[0]._size; + currentIndexBuffer.buffer = batch->_buffers.get(params[1]._int); + currentIndexBuffer.type = (Type)params[2]._int; + break; + + case Batch::Command::COMMAND_drawIndexed: + currentIndexBuffer.startIndex = params[0]._int; + currentIndexBuffer.numIndices = params[1]._int; + currentIndexBuffer.primitve = (Primitive)params[2]._int; + captured.emplace_back(currentIndexBuffer); + break; + + case Batch::Command::COMMAND_drawIndexedInstanced: + currentIndexBuffer.startIndex = params[1]._int; + currentIndexBuffer.numIndices = params[2]._int; + currentIndexBuffer.primitve = (Primitive)params[3]._int; + captured.emplace_back(currentIndexBuffer); + break; + + default: + break; + } + }); + } + + + std::string optimizedBinaryFile = basename + "_optimized.bin"; + QFile(binaryFile.c_str()).copy(optimizedBinaryFile.c_str()); + { + storage::FileStorage mappedFile(optimizedBinaryFile.c_str()); + std::set uniqueBuffers; + for (const auto& capturedIndexData : captured) { + if (uniqueBuffers.count(capturedIndexData.buffer)) { + continue; + } + uniqueBuffers.insert(capturedIndexData.buffer); + auto bufferOffset = bufferOffsets[capturedIndexData.buffer]; + auto& buffer = *capturedIndexData.buffer; + const auto& count = capturedIndexData.numIndices; + auto indices = (uint32_t*)buffer.editData(); + optimizer(capturedIndexData.primitve, count / 3, count, indices); + memcpy(mappedFile.mutableData() + bufferOffset, indices, sizeof(uint32_t) * count); + } + } + frameNode[keys::binary] = optimizedBinaryFile; + { + std::string frameJson = frameNode.dump(); + std::string filename = basename + "_optimized.json"; + storage::FileStorage::create(filename.c_str(), frameJson.size(), (const uint8_t*)frameJson.data()); + } +} diff --git a/libraries/gpu/src/gpu/FrameWriter.cpp b/libraries/gpu/src/gpu/FrameWriter.cpp new file mode 100644 index 0000000000..f348827385 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameWriter.cpp @@ -0,0 +1,844 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "FrameIO.h" +#include "Frame.h" +#include "Batch.h" +#include "TextureTable.h" +#include +#include + +#include "FrameIOKeys.h" + +namespace gpu { + +using json = nlohmann::json; + +class Serializer { +public: + const std::string basename; + const TextureCapturer textureCapturer; + std::unordered_map shaderMap; + std::unordered_map programMap; + std::unordered_map textureMap; + std::unordered_map textureTableMap; + std::unordered_map bufferMap; + std::unordered_map formatMap; + std::unordered_map pipelineMap; + std::unordered_map framebufferMap; + std::unordered_map swapchainMap; + std::unordered_map queryMap; + + Serializer(const std::string& basename, const TextureCapturer& capturer) : basename(basename), textureCapturer(capturer) {} + + template + static uint32_t getGlobalIndex(const T& value, std::unordered_map& map) { + if (map.count(value) == 0) { + uint32_t result = (uint32_t)map.size(); + map[value] = result; + return result; + } + return map[value]; + } + + template + static json serializePointerCache(const typename Batch::Cache::Vector& cache, std::unordered_map& map) { + json result = json::array(); + const auto count = cache._items.size(); + for (uint32_t i = 0; i < count; ++i) { + const auto& cacheEntry = cache._items[i]; + const auto& val = cacheEntry._data; + result.push_back(getGlobalIndex(val, map)); + } + return result; + } + + template + static json serializeDataCache(const typename Batch::Cache::Vector& cache, + std::function f = [](const T& t) -> TT { return t; }) { + json result = json::array(); + const auto count = cache._items.size(); + for (uint32_t i = 0; i < count; ++i) { + const auto& cacheEntry = cache._items[i]; + const auto& val = cacheEntry._data; + result.push_back(f(val)); + } + return result; + } + + template + static json writeVector(const std::vector& v, + const std::function& f = [](const T& t) -> TT { return t; }) { + auto node = json::array(); + for (const auto& e : v) { + node.push_back(f(e)); + } + return node; + } + + template + static json writeNumericVector(const std::vector& v) { + return writeVector(v); + } + + template + static json writeUintVector(const std::vector& v) { + return writeVector(v, [](const T& t) -> const uint32_t& { + return reinterpret_cast(t); + }); + } + + template + static json writeFloatArray(const float* f) { + json result = json::array(); + for (size_t i = 0; i < N; ++i) { + result.push_back(f[i]); + } + return result; + } + + template + static std::vector mapToVector(const std::unordered_map& map) { + std::vector result; + result.resize(map.size()); + for (const auto& entry : map) { + if (result[entry.second]) { + throw std::runtime_error("Invalid map"); + } + result[entry.second] = entry.first; + } + return result; + } + + template + std::function memberWriter(F f) { + return std::bind(f, this, std::placeholders::_1); + } + + void writeFrame(const Frame& frame); + json writeBatch(const Batch& batch); + json writeTextureTable(const TextureTablePointer& textureTable); + json writeTextureView(const TextureView& textureView); + json writeFramebuffer(const FramebufferPointer& texture); + json writePipeline(const PipelinePointer& pipeline); + json writeSwapchain(const SwapChainPointer& swapchain); + json writeProgram(const ShaderPointer& program); + json writeNamedBatchData(const Batch::NamedBatchData& namedData); + + json writeCapturableTextures(const Frame& frame); + void writeBinaryBlob(); + static std::string toBase64(const std::vector& v); + static json writeIrradiance(const SHPointer& irradiance); + static json writeMat4(const glm::mat4& m) { + static const glm::mat4 IDENTITY; + if (m == IDENTITY) { + return json(); + } + return writeFloatArray<16>(&m[0][0]); + } + static json writeVec4(const glm::vec4& v) { return writeFloatArray<4>(&v[0]); } + static json writeVec3(const glm::vec3& v) { return writeFloatArray<3>(&v[0]); } + static json writeVec2(const glm::vec2& v) { return writeFloatArray<2>(&v[0]); } + static json writeTransform(const Transform& t) { return writeMat4(t.getMatrix()); } + static json writeCommand(size_t index, const Batch& batch); + static json writeSampler(const Sampler& sampler); + static json writeTexture(const TexturePointer& texture); + static json writeFormat(const Stream::FormatPointer& format); + static json writeQuery(const QueryPointer& query); + static json writeShader(const ShaderPointer& shader); + static json writeBuffer(const BufferPointer& bufferPointer); + + static const TextureView DEFAULT_TEXTURE_VIEW; + static const Sampler DEFAULT_SAMPLER; + + template + void serializeMap(json& frameNode, const char* key, const std::unordered_map& map, F f) { + auto& node = frameNode[key] = json::array(); + for (const auto& item : mapToVector(map)) { + node.push_back(f(item)); + } + } +}; + +void writeFrame(const std::string& filename, const FramePointer& frame, const TextureCapturer& capturer) { + Serializer(filename, capturer).writeFrame(*frame); +} + +} // namespace gpu + +using namespace gpu; + +const TextureView Serializer::DEFAULT_TEXTURE_VIEW = TextureView(); +const Sampler Serializer::DEFAULT_SAMPLER = Sampler(); + +std::string Serializer::toBase64(const std::vector& v) { + return QByteArray((const char*)v.data(), (int)v.size()).toBase64().toStdString(); +} + +json Serializer::writeCommand(size_t index, const Batch& batch) { + const auto& command = batch._commands[index]; + auto offset = batch._commandOffsets[index]; + auto endOffset = batch._params.size(); + if ((index + 1) < batch._commands.size()) { + endOffset = batch._commandOffsets[index + 1]; + } + + json result = json::array(); + result.push_back(keys::COMMAND_NAMES[command]); + while (offset < endOffset) { + result.push_back(batch._params[offset]._size); + ++offset; + } + return result; +} + +json Serializer::writeNamedBatchData(const Batch::NamedBatchData& namedData) { + json result = json::object(); + auto& buffersNode = result[keys::buffers] = json::array(); + for (const auto& buffer : namedData.buffers) { + buffersNode.push_back(getGlobalIndex(buffer, bufferMap)); + } + result[keys::drawCallInfos] = writeUintVector(namedData.drawCallInfos); + return result; +} + +json Serializer::writeBatch(const Batch& batch) { + json batchNode; + static const Batch DEFAULT_BATCH; + + batchNode[keys::name] = batch.getName(); + if (batch._enableSkybox != DEFAULT_BATCH._enableSkybox) { + batchNode[keys::skybox] = batch._enableSkybox; + } + if (batch._enableStereo != DEFAULT_BATCH._enableStereo) { + batchNode[keys::stereo] = batch._enableStereo; + } + if (batch._projectionJitter != DEFAULT_BATCH._projectionJitter) { + batchNode[keys::projectionJitter] = writeVec2(batch._projectionJitter); + } + if (batch._drawcallUniform != DEFAULT_BATCH._drawcallUniform) { + batchNode[keys::drawcallUniform] = batch._drawcallUniform; + } + if (batch._drawcallUniformReset != DEFAULT_BATCH._drawcallUniformReset) { + batchNode[keys::drawcallUniformReset] = batch._drawcallUniformReset; + } + if (0 != batch._textures.size()) { + batchNode[keys::textures] = serializePointerCache(batch._textures, textureMap); + } + if (0 != batch._textureTables.size()) { + batchNode[keys::textureTables] = serializePointerCache(batch._textureTables, textureTableMap); + } + if (0 != batch._buffers.size()) { + batchNode[keys::buffers] = serializePointerCache(batch._buffers, bufferMap); + } + if (0 != batch._pipelines.size()) { + batchNode[keys::pipelines] = serializePointerCache(batch._pipelines, pipelineMap); + } + if (0 != batch._streamFormats.size()) { + batchNode[keys::formats] = serializePointerCache(batch._streamFormats, formatMap); + } + if (0 != batch._framebuffers.size()) { + batchNode[keys::framebuffers] = serializePointerCache(batch._framebuffers, framebufferMap); + } + if (0 != batch._swapChains.size()) { + batchNode[keys::swapchains] = serializePointerCache(batch._swapChains, swapchainMap); + } + if (0 != batch._queries.size()) { + batchNode[keys::queries] = serializePointerCache(batch._queries, queryMap); + } + if (!batch._drawCallInfos.empty()) { + batchNode[keys::drawCallInfos] = writeUintVector(batch._drawCallInfos); + } + if (!batch._data.empty()) { + batchNode[keys::data] = toBase64(batch._data); + } + + { + auto& node = batchNode[keys::commands] = json::array(); + size_t commandCount = batch._commands.size(); + for (size_t i = 0; i < commandCount; ++i) { + node.push_back(writeCommand(i, batch)); + } + } + + if (0 != batch._transforms.size()) { + batchNode[keys::transforms] = + serializeDataCache(batch._transforms, [](const Transform& t) { return writeTransform(t); }); + } + if (0 != batch._profileRanges.size()) { + batchNode[keys::profileRanges] = serializeDataCache(batch._profileRanges); + } + if (0 != batch._names.size()) { + batchNode[keys::names] = serializeDataCache(batch._names); + } + if (0 != batch._objects.size()) { + auto transform = [](const Batch::TransformObject& object) -> json { return writeMat4(object._model); }; + batchNode[keys::objects] = writeVector(batch._objects, transform); + } + + if (!batch._namedData.empty()) { + auto& namedDataNode = batchNode[keys::namedData] = json::object(); + for (const auto& entry : batch._namedData) { + namedDataNode[entry.first] = writeNamedBatchData(entry.second); + } + } + + // LambdaCache _lambdas; + + return batchNode; +} + +json Serializer::writeSampler(const Sampler& sampler) { + json result = json::object(); + if (sampler.getBorderColor() != DEFAULT_SAMPLER.getBorderColor()) { + result[keys::borderColor] = writeVec4(sampler.getBorderColor()); + } + if (sampler.getMaxAnisotropy() != DEFAULT_SAMPLER.getMaxAnisotropy()) { + result[keys::maxAnisotropy] = sampler.getMaxAnisotropy(); + } + if (sampler.getWrapModeU() != DEFAULT_SAMPLER.getWrapModeU()) { + result[keys::wrapModeU] = sampler.getWrapModeU(); + } + if (sampler.getWrapModeV() != DEFAULT_SAMPLER.getWrapModeV()) { + result[keys::wrapModeV] = sampler.getWrapModeV(); + } + if (sampler.getWrapModeW() != DEFAULT_SAMPLER.getWrapModeW()) { + result[keys::wrapModeW] = sampler.getWrapModeW(); + } + if (sampler.getFilter() != DEFAULT_SAMPLER.getFilter()) { + result[keys::filter] = sampler.getFilter(); + } + if (sampler.getComparisonFunction() != DEFAULT_SAMPLER.getComparisonFunction()) { + result[keys::comparisonFunction] = sampler.getComparisonFunction(); + } + if (sampler.getMinMip() != DEFAULT_SAMPLER.getMinMip()) { + result[keys::minMip] = sampler.getMinMip(); + } + if (sampler.getMaxMip() != DEFAULT_SAMPLER.getMaxMip()) { + result[keys::maxMip] = sampler.getMaxMip(); + } + if (sampler.getMipOffset() != DEFAULT_SAMPLER.getMipOffset()) { + result[keys::mipOffset] = sampler.getMipOffset(); + } + return result; +} + +json Serializer::writeBuffer(const BufferPointer& bufferPointer) { + if (!bufferPointer) { + return json(); + } + + return json(bufferPointer->getSize()); +} + +json Serializer::writeIrradiance(const SHPointer& irradiancePointer) { + if (!irradiancePointer) { + return json(); + } + + json result = json::object(); + const auto& irradiance = *irradiancePointer; + result[keys::L00] = writeVec3(irradiance.L00); + result[keys::L1m1] = writeVec3(irradiance.L1m1); + result[keys::L10] = writeVec3(irradiance.L10); + result[keys::L11] = writeVec3(irradiance.L11); + result[keys::L2m2] = writeVec3(irradiance.L2m2); + result[keys::L2m1] = writeVec3(irradiance.L2m1); + result[keys::L20] = writeVec3(irradiance.L20); + result[keys::L21] = writeVec3(irradiance.L21); + result[keys::L22] = writeVec3(irradiance.L22); + return result; +} + +json Serializer::writeTexture(const TexturePointer& texturePointer) { + if (!texturePointer) { + return json(); + } + + const auto& texture = *texturePointer; + json result = json::object(); + if (!texture.source().empty()) { + result[keys::source] = texture.source(); + } + const auto usageType = texture.getUsageType(); + result[keys::usageType] = usageType; + result[keys::type] = texture.getType(); + result[keys::width] = texture._width; + result[keys::height] = texture._height; + result[keys::depth] = texture._depth; + result[keys::mips] = texture._maxMipLevel + 1; + result[keys::samples] = texture._numSamples; + result[keys::layers] = texture._numSlices; + result[keys::texelFormat] = texture.getTexelFormat().getRaw(); + if (texture.isIrradianceValid()) { + result["irradiance"] = writeIrradiance(texture._irradiance); + } + if (texture._sampler != DEFAULT_SAMPLER) { + result[keys::sampler] = writeSampler(texture._sampler); + } + if (usageType == TextureUsageType::RENDERBUFFER) { + // TODO figure out if the buffer contents need to be preserved (if it's used as an input before it's ever written to) + // This might be the case for things like the TAA output attachments from the previous frame + } else if (usageType == TextureUsageType::EXTERNAL) { + // TODO serialize the current GL contents (if any) to the JSON + } else { + const auto* storage = texture._storage.get(); + const auto* ktxStorage = dynamic_cast(storage); + if (ktxStorage) { + result[keys::ktxFile] = ktxStorage->_filename; + } else { + // TODO serialize the backing storage + } + } + return result; +} + +json Serializer::writeTextureView(const TextureView& textureView) { + static const auto DEFAULT_TEXTURE_VIEW = TextureView(); + json result = json::object(); + if (textureView._texture) { + result[keys::texture] = getGlobalIndex(textureView._texture, textureMap); + } + if (textureView._subresource != DEFAULT_TEXTURE_VIEW._subresource) { + result[keys::subresource] = textureView._subresource; + } + if (textureView._element != DEFAULT_TEXTURE_VIEW._element) { + result[keys::element] = textureView._element.getRaw(); + } + return result; +} + +json Serializer::writeFramebuffer(const FramebufferPointer& framebufferPointer) { + if (!framebufferPointer) { + return json(); + } + + auto result = json::object(); + const auto& framebuffer = *framebufferPointer; + if (!framebuffer._name.empty()) { + result[keys::name] = framebuffer._name; + } + if (framebuffer._bufferMask != 0) { + result[keys::bufferMask] = framebuffer._bufferMask; + } + if (framebuffer._height != 0) { + result[keys::height] = framebuffer._height; + } + if (framebuffer._width != 0) { + result[keys::width] = framebuffer._width; + } + if (framebuffer._numSamples != 0 && framebuffer._numSamples != 1) { + result[keys::sampleCount] = framebuffer._numSamples; + } + + if (framebuffer._depthStencilBuffer.isValid()) { + result[keys::depthStencilAttachment] = writeTextureView(framebuffer._depthStencilBuffer); + } + + if (!framebuffer._renderBuffers.empty()) { + size_t rendereBufferCount = 0; + for (size_t i = 0; i < framebuffer._renderBuffers.size(); ++i) { + if (framebuffer._renderBuffers[i].isValid()) { + rendereBufferCount = i + 1; + } + } + if (rendereBufferCount > 0) { + auto& node = result[keys::colorAttachments] = json::array(); + for (size_t i = 0; i < rendereBufferCount; ++i) { + node.push_back(writeTextureView(framebuffer._renderBuffers[i])); + } + } + } + + //SwapchainPointer _swapchain; + return result; +} + +json Serializer::writeTextureTable(const TextureTablePointer& textureTablePointer) { + auto tableNode = json::array(); + const auto& textureTable = *textureTablePointer; + for (const auto& texture : textureTable.getTextures()) { + tableNode.push_back(getGlobalIndex(texture, textureMap)); + } + return tableNode; +} + +json Serializer::writeFormat(const Stream::FormatPointer& formatPointer) { + if (!formatPointer) { + return json(); + } + + const auto& format = *formatPointer; + json result = json::object(); + auto& attributesNode = result[keys::attributes] = json::array(); + static const auto DEFAULT_ATTRIBUTE = Stream::Attribute(); + for (const auto& entry : format._attributes) { + const auto& attribute = entry.second; + auto attributeNode = json::object(); + attributeNode[keys::slot] = attribute._slot; + attributeNode[keys::channel] = attribute._channel; + if (DEFAULT_ATTRIBUTE._element.getRaw() != attribute._element.getRaw()) { + attributeNode[keys::element] = attribute._element.getRaw(); + } + if (DEFAULT_ATTRIBUTE._frequency != attribute._frequency) { + attributeNode[keys::frequency] = attribute._frequency; + } + if (DEFAULT_ATTRIBUTE._offset != attribute._offset) { + attributeNode[keys::offset] = attribute._offset; + } + attributesNode.push_back(attributeNode); + } + return result; +} + +#define SET_IF_NOT_DEFAULT(FIELD) \ + if (value.FIELD != DEFAULT.FIELD) { \ + result[keys::FIELD] = value.FIELD; \ + } + +#define SET_IF_NOT_DEFAULT_(FIELD) \ + if (value._##FIELD != DEFAULT._##FIELD) { \ + result[keys::FIELD] = value._##FIELD; \ + } + +#define SET_IF_NOT_DEFAULT_TRANSFORM(FIELD, TRANSFORM) \ + if (value.FIELD != DEFAULT.FIELD) { \ + result[keys::FIELD] = TRANSFORM(value.FIELD); \ + } + +static json writeBlendFunction(const State::BlendFunction& value) { + static const State::BlendFunction DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(enabled); + SET_IF_NOT_DEFAULT(sourceColor); + SET_IF_NOT_DEFAULT(sourceAlpha); + SET_IF_NOT_DEFAULT(destColor); + SET_IF_NOT_DEFAULT(destAlpha); + SET_IF_NOT_DEFAULT(destColor); + SET_IF_NOT_DEFAULT(opAlpha); + SET_IF_NOT_DEFAULT(opColor); + return result; +} + +static json writeStateFlags(const State::Flags& value) { + static const State::Flags DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(frontFaceClockwise); + SET_IF_NOT_DEFAULT(depthClampEnable); + SET_IF_NOT_DEFAULT(scissorEnable); + SET_IF_NOT_DEFAULT(multisampleEnable); + SET_IF_NOT_DEFAULT(antialisedLineEnable); + SET_IF_NOT_DEFAULT(alphaToCoverageEnable); + return result; +} + +static json writeDepthTest(const State::DepthTest& value) { + static const State::DepthTest DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(writeMask); + SET_IF_NOT_DEFAULT(enabled); + SET_IF_NOT_DEFAULT(function); + return result; +} + + +static json writeStereoState(const StereoState& value) { + static const StereoState DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT_(enable); + SET_IF_NOT_DEFAULT_(contextDisable); + SET_IF_NOT_DEFAULT_(skybox); + if ((value._eyeProjections[0] != DEFAULT._eyeProjections[0]) || (value._eyeProjections[1] != DEFAULT._eyeProjections[1])) { + json projections = json::array(); + projections.push_back(Serializer::writeMat4(value._eyeProjections[0])); + projections.push_back(Serializer::writeMat4(value._eyeProjections[1])); + result[keys::eyeProjections] = projections; + } + if ((value._eyeViews[0] != DEFAULT._eyeViews[0]) || (value._eyeViews[1] != DEFAULT._eyeViews[1])) { + json views = json::array(); + views.push_back(Serializer::writeMat4(value._eyeViews[0])); + views.push_back(Serializer::writeMat4(value._eyeViews[1])); + result[keys::eyeViews] = views; + } + return result; +} + +static json writeStencilTest(const State::StencilTest& value) { + static const State::StencilTest DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(function); + SET_IF_NOT_DEFAULT(failOp); + SET_IF_NOT_DEFAULT(depthFailOp); + SET_IF_NOT_DEFAULT(passOp); + SET_IF_NOT_DEFAULT(reference); + SET_IF_NOT_DEFAULT(readMask); + return result; +} + +static json writeStencilActivation(const State::StencilActivation& value) { + static const State::StencilActivation DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(frontWriteMask); + SET_IF_NOT_DEFAULT(backWriteMask); + SET_IF_NOT_DEFAULT(enabled); + return result; +} + +json writeState(const StatePointer& statePointer) { + if (!statePointer) { + return json(); + } + const auto& state = *statePointer; + const auto& value = state.getValues(); + const auto& DEFAULT = State::DEFAULT; + auto result = json::object(); + SET_IF_NOT_DEFAULT(colorWriteMask); + SET_IF_NOT_DEFAULT(cullMode); + SET_IF_NOT_DEFAULT(depthBias); + SET_IF_NOT_DEFAULT(depthBiasSlopeScale); + SET_IF_NOT_DEFAULT(fillMode); + SET_IF_NOT_DEFAULT(sampleMask); + SET_IF_NOT_DEFAULT_TRANSFORM(blendFunction, writeBlendFunction); + SET_IF_NOT_DEFAULT_TRANSFORM(flags, writeStateFlags); + SET_IF_NOT_DEFAULT_TRANSFORM(depthTest, writeDepthTest); + SET_IF_NOT_DEFAULT_TRANSFORM(stencilActivation, writeStencilActivation); + SET_IF_NOT_DEFAULT_TRANSFORM(stencilTestFront, writeStencilTest); + SET_IF_NOT_DEFAULT_TRANSFORM(stencilTestBack, writeStencilTest); + return result; +} + +json Serializer::writePipeline(const PipelinePointer& pipelinePointer) { + if (!pipelinePointer) { + return json(); + } + const auto& pipeline = *pipelinePointer; + auto result = json::object(); + result[keys::state] = writeState(pipeline.getState()); + result[keys::program] = getGlobalIndex(pipeline.getProgram(), programMap); + return result; +} + +json Serializer::writeProgram(const ShaderPointer& programPointer) { + if (!programPointer) { + return json(); + } + const auto& program = *programPointer; + auto result = json::array(); + for (const auto& shader : program._shaders) { + result.push_back(getGlobalIndex(shader, shaderMap)); + } + return result; +} + +json Serializer::writeShader(const ShaderPointer& shaderPointer) { + if (!shaderPointer) { + return json(); + } + auto result = json::object(); + const auto& shader = *shaderPointer; + result[keys::id] = shader._source.id; + result[keys::name] = shader._source.name; + result[keys::type] = shader._type; + return result; +} + +json Serializer::writeSwapchain(const SwapChainPointer& swapchainPointer) { + auto framebufferSwapchainPointer = std::static_pointer_cast(swapchainPointer); + if (!framebufferSwapchainPointer) { + return json(); + } + const FramebufferSwapChain& swapchain = *framebufferSwapchainPointer; + auto result = json::object(); + result[keys::size] = swapchain.getSize(); + auto& framebuffersNode = result[keys::framebuffers] = json::array(); + for (uint32_t i = 0; i < swapchain.getSize(); ++i) { + uint32_t index = getGlobalIndex(swapchain.get(i), framebufferMap); + framebuffersNode.push_back(index); + } + return result; +} + +json Serializer::writeQuery(const QueryPointer& queryPointer) { + if (!queryPointer) { + return json(); + } + + const auto& query = *queryPointer; + auto result = json::object(); + result[keys::name] = query._name; + return result; +} + +json Serializer::writeCapturableTextures(const Frame& frame) { + if (!textureCapturer) { + return json::array(); + } + + std::unordered_set writtenRenderbuffers; + std::unordered_set captureTextures; + + auto maybeCaptureTexture = [&](const TexturePointer& texture) { + // Not a valid texture + if (!texture) { + return; + } + + // Not a renderbuffer + if (texture->getUsageType() != TextureUsageType::RENDERBUFFER) { + return; + } + + // Already used in a target framebuffer + if (writtenRenderbuffers.count(texture) > 0) { + return; + } + + captureTextures.insert(texture); + }; + + for (const auto& batchPtr : frame.batches) { + const auto& batch = *batchPtr; + batch.forEachCommand([&](Batch::Command command, const Batch::Param* params) { + switch (command) { + case Batch::COMMAND_setResourceTexture: { + const auto& texture = batch._textures.get(params[0]._uint); + maybeCaptureTexture(texture); + } break; + + case Batch::COMMAND_setResourceTextureTable: { + const auto& textureTablePointer = batch._textureTables.get(params[0]._uint); + if (textureTablePointer) { + for (const auto& texture : textureTablePointer->getTextures()) { + maybeCaptureTexture(texture); + } + } + } break; + + case Batch::COMMAND_setFramebuffer: { + const auto& framebufferPointer = batch._framebuffers.get(params[0]._uint); + if (framebufferPointer) { + const auto& framebuffer = *framebufferPointer; + for (const auto& colorAttachment : framebuffer._renderBuffers) { + if (colorAttachment._texture) { + writtenRenderbuffers.insert(colorAttachment._texture); + } + } + if (framebuffer._depthStencilBuffer._texture) { + writtenRenderbuffers.insert(framebuffer._depthStencilBuffer._texture); + } + } + } + + case Batch::COMMAND_setResourceFramebufferSwapChainTexture: + default: + break; + } + }); // for each command + } // for each batch + + for (const auto& entry : textureMap) { + const auto& texturePointer = entry.first; + if (!texturePointer) { + continue; + } + const auto& texture = *texturePointer; + auto usageType = texture.getUsageType(); + if (usageType == TextureUsageType::RESOURCE || usageType == TextureUsageType::STRICT_RESOURCE) { + const auto* storage = texture._storage.get(); + const auto* ktxStorage = dynamic_cast(storage); + if (!ktxStorage) { + captureTextures.insert(texturePointer); + } + } + } + + json result = json::array(); + for (const auto& texture : captureTextures) { + if (textureCapturer) { + auto index = textureMap[texture]; + auto layers = std::max(texture->getNumSlices(), 1); + for (uint16 layer = 0; layer < layers; ++layer) { + std::string textureFilename = basename + "." + std::to_string(index) + "." + std::to_string(layer) + ".png"; + textureCapturer(textureFilename, texture, layer); + } + result.push_back(index); + } + } + return result; +} + +void Serializer::writeFrame(const Frame& frame) { + json frameNode = json::object(); + + frameNode[keys::batches] = json::array(); + for (const auto& batchPointer : frame.batches) { + frameNode[keys::batches].push_back(writeBatch(*batchPointer)); + } + + frameNode[keys::stereo] = writeStereoState(frame.stereoState); + frameNode[keys::capturedTextures] = writeCapturableTextures(frame); + frameNode[keys::frameIndex] = frame.frameIndex; + frameNode[keys::view] = writeMat4(frame.view); + frameNode[keys::pose] = writeMat4(frame.pose); + frameNode[keys::framebuffer] = getGlobalIndex(frame.framebuffer, framebufferMap); + using namespace std::placeholders; + serializeMap(frameNode, keys::swapchains, swapchainMap, std::bind(&Serializer::writeSwapchain, this, _1)); + serializeMap(frameNode, keys::framebuffers, framebufferMap, [this](const auto& t) { return writeFramebuffer(t); }); + serializeMap(frameNode, keys::textureTables, textureTableMap, [this](const auto& t) { return writeTextureTable(t); }); + serializeMap(frameNode, keys::pipelines, pipelineMap, [this](const auto& t) { return writePipeline(t); }); + serializeMap(frameNode, keys::programs, programMap, [this](const auto& t) { return writeProgram(t); }); + serializeMap(frameNode, keys::shaders, shaderMap, writeShader); + serializeMap(frameNode, keys::queries, queryMap, writeQuery); + serializeMap(frameNode, keys::formats, formatMap, writeFormat); + + // Serialize textures and buffers last, since the maps they use can be populated by some of the above code + // Serialize textures + serializeMap(frameNode, keys::textures, textureMap, writeTexture); + // Serialize buffers + serializeMap(frameNode, keys::buffers, bufferMap, writeBuffer); + + { + std::string frameJson = frameNode.dump(); + std::string filename = basename + ".json"; + storage::FileStorage::create(filename.c_str(), frameJson.size(), (const uint8_t*)frameJson.data()); + } + + writeBinaryBlob(); + frameNode[keys::binary] = basename + ".bin"; +} + +void Serializer::writeBinaryBlob() { + const auto buffers = mapToVector(bufferMap); + auto accumulator = [](size_t total, const BufferPointer& buffer) { return total + (buffer ? buffer->getSize() : 0); }; + size_t totalSize = std::accumulate(buffers.begin(), buffers.end(), (size_t)0, accumulator); + + const auto blobFilename = basename + ".bin"; + QFile file(blobFilename.c_str()); + if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) { + throw std::runtime_error("Unable to open file for writing"); + } + if (!file.resize(totalSize)) { + throw std::runtime_error("Unable to resize file"); + } + + auto mapped = file.map(0, totalSize); + size_t offset = 0; + + for (const auto& bufferPointer : buffers) { + if (!bufferPointer) { + continue; + } + const auto& buffer = *bufferPointer; + const auto bufferSize = buffer.getSize(); + const auto& bufferData = buffer._renderSysmem.readData(); + memcpy(mapped + offset, bufferData, bufferSize); + offset += bufferSize; + } + if (!file.unmap(mapped)) { + throw std::runtime_error("Unable to unmap file"); + } +} diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index 44e945883f..47b2775d52 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -173,6 +173,8 @@ protected: void updateSize(const TexturePointer& texture); bool assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource); + friend class Serializer; + friend class Deserializer; // Non exposed Framebuffer(const Framebuffer& framebuffer) = delete; Framebuffer() {} diff --git a/libraries/gpu/src/gpu/PointerStorage.h b/libraries/gpu/src/gpu/PointerStorage.h new file mode 100644 index 0000000000..3854205efb --- /dev/null +++ b/libraries/gpu/src/gpu/PointerStorage.h @@ -0,0 +1,159 @@ +// +// Created by Bradley Austin Davis on 2019/01/25 +// Copyright 2013-2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_gpu_PointerStorage_h +#define hifi_gpu_PointerStorage_h + +namespace gpu { + +// +// GL Backend pointer storage mechanism +// One of the following three defines must be defined. +// GPU_POINTER_STORAGE_SHARED + +// The platonic ideal, use references to smart pointers. +// However, this produces artifacts because there are too many places in the code right now that +// create temporary values (undesirable smart pointer duplications) and then those temp variables +// get passed on and have their reference taken, and then invalidated +// GPU_POINTER_STORAGE_REF + +// Raw pointer manipulation. Seems more dangerous than the reference wrappers, +// but in practice, the danger of grabbing a reference to a temporary variable +// is causing issues +// GPU_POINTER_STORAGE_RAW + +#if defined(GPU_POINTER_STORAGE_SHARED) +template +static inline bool compare(const std::shared_ptr& a, const std::shared_ptr& b) { + return a == b; +} + +template +static inline T* acquire(const std::shared_ptr& pointer) { + return pointer.get(); +} + +template +static inline void reset(std::shared_ptr& pointer) { + return pointer.reset(); +} + +template +static inline bool valid(const std::shared_ptr& pointer) { + return pointer.operator bool(); +} + +template +static inline void assign(std::shared_ptr& pointer, const std::shared_ptr& source) { + pointer = source; +} + +using BufferReference = BufferPointer; +using TextureReference = TexturePointer; +using FramebufferReference = FramebufferPointer; +using FormatReference = Stream::FormatPointer; +using PipelineReference = PipelinePointer; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#elif defined(GPU_POINTER_STORAGE_REF) + +template +class PointerReferenceWrapper : public std::reference_wrapper> { + using Parent = std::reference_wrapper>; + +public: + using Pointer = std::shared_ptr; + PointerReferenceWrapper() : Parent(EMPTY()) {} + PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {} + void clear() { *this = EMPTY(); } + +private: + static const Pointer& EMPTY() { + static const Pointer EMPTY_VALUE; + return EMPTY_VALUE; + }; +}; + +template +static bool compare(const PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + return reference.get() == pointer; +} + +template +static inline T* acquire(const PointerReferenceWrapper& reference) { + return reference.get().get(); +} + +template +static void assign(PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + reference = pointer; +} + +template +static bool valid(const PointerReferenceWrapper& reference) { + return reference.get().operator bool(); +} + +template +static inline void reset(PointerReferenceWrapper& reference) { + return reference.clear(); +} + +using BufferReference = PointerReferenceWrapper; +using TextureReference = PointerReferenceWrapper; +using FramebufferReference = PointerReferenceWrapper; +using FormatReference = PointerReferenceWrapper; +using PipelineReference = PointerReferenceWrapper; + +#define GPU_REFERENCE_INIT_VALUE + +#elif defined(GPU_POINTER_STORAGE_RAW) + +template +static bool compare(const T* const& rawPointer, const std::shared_ptr& pointer) { + return rawPointer == pointer.get(); +} + +template +static inline T* acquire(T* const& rawPointer) { + return rawPointer; +} + +template +static inline bool valid(const T* const& rawPointer) { + return rawPointer; +} + +template +static inline void reset(T*& rawPointer) { + rawPointer = nullptr; +} + +template +static inline void assign(T*& rawPointer, const std::shared_ptr& pointer) { + rawPointer = pointer.get(); +} + +using BufferReference = Buffer*; +using TextureReference = Texture*; +using FramebufferReference = Framebuffer*; +using FormatReference = Stream::Format*; +using PipelineReference = Pipeline*; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#else + +#error "No GPU pointer storage scheme defined" + +#endif + +} + +#endif // hifi_gpu_PointerStorage_h \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index e053fdd507..912901951c 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -45,6 +45,9 @@ namespace gpu { const std::string _name; uint64_t _queryResult { 0 }; uint64_t _usecBatchElapsedTime { 0 }; + + friend class Serializer; + friend class Deserializer; }; typedef std::shared_ptr QueryPointer; diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 35426bed20..987e632025 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -167,6 +167,8 @@ protected: const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader); + friend class Serializer; + friend class Deserializer; }; typedef Shader::Pointer ShaderPointer; diff --git a/libraries/gpu/src/gpu/State.cpp b/libraries/gpu/src/gpu/State.cpp index cb5ced2c15..fab0438f75 100755 --- a/libraries/gpu/src/gpu/State.cpp +++ b/libraries/gpu/src/gpu/State.cpp @@ -33,19 +33,19 @@ State::Signature State::evalSignature(const Data& state) { if (state.cullMode != State::DEFAULT.cullMode) { signature.set(State::CULL_MODE); } - if (state.frontFaceClockwise != State::DEFAULT.frontFaceClockwise) { + if (state.flags.frontFaceClockwise != State::DEFAULT.flags.frontFaceClockwise) { signature.set(State::FRONT_FACE_CLOCKWISE); } - if (state.depthClampEnable != State::DEFAULT.depthClampEnable) { + if (state.flags.depthClampEnable != State::DEFAULT.flags.depthClampEnable) { signature.set(State::DEPTH_CLAMP_ENABLE); } - if (state.scissorEnable != State::DEFAULT.scissorEnable) { + if (state.flags.scissorEnable != State::DEFAULT.flags.scissorEnable) { signature.set(State::SCISSOR_ENABLE); } - if (state.multisampleEnable != State::DEFAULT.multisampleEnable) { + if (state.flags.multisampleEnable != State::DEFAULT.flags.multisampleEnable) { signature.set(State::MULTISAMPLE_ENABLE); } - if (state.antialisedLineEnable != State::DEFAULT.antialisedLineEnable) { + if (state.flags.antialisedLineEnable != State::DEFAULT.flags.antialisedLineEnable) { signature.set(State::ANTIALISED_LINE_ENABLE); } if (state.depthBias != State::DEFAULT.depthBias) { @@ -69,7 +69,7 @@ State::Signature State::evalSignature(const Data& state) { if (state.sampleMask != State::DEFAULT.sampleMask) { signature.set(State::SAMPLE_MASK); } - if (state.alphaToCoverageEnable != State::DEFAULT.alphaToCoverageEnable) { + if (state.flags.alphaToCoverageEnable != State::DEFAULT.flags.alphaToCoverageEnable) { signature.set(State::ALPHA_TO_COVERAGE_ENABLE); } if (state.blendFunction != State::DEFAULT.blendFunction) { @@ -86,3 +86,28 @@ State::State(const Data& values) : _values(values) { _signature = evalSignature(_values); } + + +template +static std::string hex(T t) { + std::stringstream stream; + stream << std::hex << t; + return stream.str(); +} + +std::string State::getKey() const { + std::string key; + key = hex(*(int*)&_values.depthBias); + key += ":" + hex(*(int*)&_values.depthBiasSlopeScale); + key += ":" + hex(_values.depthTest.getRaw()); + key += ":" + hex(_values.stencilActivation.getRaw()); + key += ":" + hex(_values.stencilTestFront.getRaw()); + key += ":" + hex(_values.stencilTestBack.getRaw()); + key += ":" + hex(_values.blendFunction.getRaw()); + key += ":" + hex(_values.sampleMask); + // fillMode, cullMode, colorMaskWrite and the flags consume 32 bits alltogether + static_assert(0 == offsetof(State::Data, fillMode) % 4, "Validate fillMode offset"); + key += ":" + hex(*(int*)&_values.fillMode); + return key; +} + diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index e68e829fb5..abe0cd7731 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -14,6 +14,7 @@ #include "Format.h" #include +#include #include #include #include @@ -21,16 +22,16 @@ // Why a macro and not a fancy template you will ask me ? // Because some of the fields are bool packed tightly in the State::Cache class // and it s just not good anymore for template T& variable manipulation... -#define SET_FIELD(field, defaultValue, value, dest) {\ - dest = value;\ - if (value == defaultValue) {\ - _signature.reset(field);\ - } else {\ - _signature.set(field);\ - }\ - _stamp++;\ -}\ - +#define SET_FIELD(FIELD, PATH, value) \ + { \ + _values.PATH = value; \ + if (value == DEFAULT.PATH) { \ + _signature.reset(FIELD); \ + } else { \ + _signature.set(FIELD); \ + } \ + _stamp++; \ + } namespace gpu { @@ -45,7 +46,8 @@ public: typedef ::gpu::ComparisonFunction ComparisonFunction; - enum FillMode { + enum FillMode : uint8 + { FILL_POINT = 0, FILL_LINE, FILL_FACE, @@ -53,7 +55,8 @@ public: NUM_FILL_MODES, }; - enum CullMode { + enum CullMode : uint8 + { CULL_NONE = 0, CULL_FRONT, CULL_BACK, @@ -61,7 +64,8 @@ public: NUM_CULL_MODES, }; - enum StencilOp { + enum StencilOp : uint16 + { STENCIL_OP_KEEP = 0, STENCIL_OP_ZERO, STENCIL_OP_REPLACE, @@ -74,7 +78,8 @@ public: NUM_STENCIL_OPS, }; - enum BlendArg { + enum BlendArg : uint16 + { ZERO = 0, ONE, SRC_COLOR, @@ -94,7 +99,8 @@ public: NUM_BLEND_ARGS, }; - enum BlendOp { + enum BlendOp : uint16 + { BLEND_OP_ADD = 0, BLEND_OP_SUBTRACT, BLEND_OP_REV_SUBTRACT, @@ -104,132 +110,158 @@ public: NUM_BLEND_OPS, }; - enum ColorMask + enum ColorMask : uint8 { WRITE_NONE = 0, WRITE_RED = 1, WRITE_GREEN = 2, WRITE_BLUE = 4, WRITE_ALPHA = 8, - WRITE_ALL = (WRITE_RED | WRITE_GREEN | WRITE_BLUE | WRITE_ALPHA ), + WRITE_ALL = (WRITE_RED | WRITE_GREEN | WRITE_BLUE | WRITE_ALPHA), }; class DepthTest { - uint8 _function = LESS; - uint8 _writeMask = true; - uint8 _enabled = false; -#if defined(__clang__) - __attribute__((unused)) -#endif - uint8 _spare = 0; // Padding + public: + uint8 writeMask{ true }; + uint8 enabled{ false }; + ComparisonFunction function{ LESS }; + public: DepthTest(bool enabled = false, bool writeMask = true, ComparisonFunction func = LESS) : - _function(func), _writeMask(writeMask), _enabled(enabled) { - } + writeMask(writeMask), enabled(enabled), function(func) {} - bool isEnabled() const { return _enabled != 0; } - ComparisonFunction getFunction() const { return ComparisonFunction(_function); } - uint8 getWriteMask() const { return _writeMask; } + bool isEnabled() const { return enabled != 0; } + ComparisonFunction getFunction() const { return function; } + uint8 getWriteMask() const { return writeMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } DepthTest(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const DepthTest& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const DepthTest& right) const { return getRaw() != right.getRaw(); } - }; + bool operator==(const DepthTest& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const DepthTest& right) const { return getRaw() != right.getRaw(); } + }; - class StencilTest { - static const int FUNC_MASK = 0x000f; - static const int FAIL_OP_MASK = 0x00f0; - static const int DEPTH_FAIL_OP_MASK = 0x0f00; - static const int PASS_OP_MASK = 0xf000; - static const int FAIL_OP_OFFSET = 4; - static const int DEPTH_FAIL_OP_OFFSET = 8; - static const int PASS_OP_OFFSET = 12; + static_assert(sizeof(DepthTest) == sizeof(uint32_t), "DepthTest size check"); - uint16 _functionAndOperations; - int8 _reference = 0; - uint8 _readMask = 0xff; - public: + struct StencilTest { + ComparisonFunction function : 4; + StencilOp failOp : 4; + StencilOp depthFailOp : 4; + StencilOp passOp : 4; + int8 reference{ 0 }; + uint8 readMask{ 0xff }; - StencilTest(int8 reference = 0, uint8 readMask =0xFF, ComparisonFunction func = ALWAYS, StencilOp failOp = STENCIL_OP_KEEP, StencilOp depthFailOp = STENCIL_OP_KEEP, StencilOp passOp = STENCIL_OP_KEEP) : - _functionAndOperations(func | (failOp << FAIL_OP_OFFSET) | (depthFailOp << DEPTH_FAIL_OP_OFFSET) | (passOp << PASS_OP_OFFSET)), - _reference(reference), _readMask(readMask) - {} + public: + StencilTest(int8 reference = 0, + uint8 readMask = 0xFF, + ComparisonFunction func = ALWAYS, + StencilOp failOp = STENCIL_OP_KEEP, + StencilOp depthFailOp = STENCIL_OP_KEEP, + StencilOp passOp = STENCIL_OP_KEEP) : + function(func), + failOp(failOp), depthFailOp(depthFailOp), passOp(passOp), reference(reference), readMask(readMask) {} - ComparisonFunction getFunction() const { return ComparisonFunction(_functionAndOperations & FUNC_MASK); } - StencilOp getFailOp() const { return StencilOp((_functionAndOperations & FAIL_OP_MASK) >> FAIL_OP_OFFSET); } - StencilOp getDepthFailOp() const { return StencilOp((_functionAndOperations & DEPTH_FAIL_OP_MASK) >> DEPTH_FAIL_OP_OFFSET); } - StencilOp getPassOp() const { return StencilOp((_functionAndOperations & PASS_OP_MASK) >> PASS_OP_OFFSET); } + ComparisonFunction getFunction() const { return function; } + StencilOp getFailOp() const { return failOp; } + StencilOp getDepthFailOp() const { return depthFailOp; } + StencilOp getPassOp() const { return passOp; } - int8 getReference() const { return _reference; } - uint8 getReadMask() const { return _readMask; } + int8 getReference() const { return reference; } + uint8 getReadMask() const { return readMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } StencilTest(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const StencilTest& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const StencilTest& right) const { return getRaw() != right.getRaw(); } - }; + bool operator==(const StencilTest& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const StencilTest& right) const { return getRaw() != right.getRaw(); } + }; + static_assert(sizeof(StencilTest) == sizeof(uint32_t), "StencilTest size check"); - class StencilActivation { - uint8 _frontWriteMask = 0xFF; - uint8 _backWriteMask = 0xFF; - uint16 _enabled = 0; - public: + StencilTest stencilTestFront; - StencilActivation(bool enabled, uint8 frontWriteMask = 0xFF, uint8 backWriteMask = 0xFF) : - _frontWriteMask(frontWriteMask), _backWriteMask(backWriteMask), _enabled(enabled) {} + struct StencilActivation { + uint8 frontWriteMask = 0xFF; + uint8 backWriteMask = 0xFF; + bool enabled : 1; + uint8 _spare1 : 7; + uint8 _spare2{ 0 }; - bool isEnabled() const { return (_enabled != 0); } - uint8 getWriteMaskFront() const { return _frontWriteMask; } - uint8 getWriteMaskBack() const { return _backWriteMask; } + public: + StencilActivation(bool enabled = false, uint8 frontWriteMask = 0xFF, uint8 backWriteMask = 0xFF) : + frontWriteMask(frontWriteMask), backWriteMask(backWriteMask), enabled(enabled), _spare1{ 0 } {} + + bool isEnabled() const { return enabled; } + uint8 getWriteMaskFront() const { return frontWriteMask; } + uint8 getWriteMaskBack() const { return backWriteMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } StencilActivation(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const StencilActivation& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const StencilActivation& right) const { return getRaw() != right.getRaw(); } + bool operator==(const StencilActivation& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const StencilActivation& right) const { return getRaw() != right.getRaw(); } }; - class BlendFunction { - static const int COLOR_MASK = 0x0f; - static const int ALPHA_MASK = 0xf0; - static const int ALPHA_OFFSET = 4; + static_assert(sizeof(StencilActivation) == sizeof(uint32_t), "StencilActivation size check"); + + struct BlendFunction { + // Using uint8 here will make the structure as a whole not align to 32 bits + uint16 enabled : 8; + BlendArg sourceColor : 4; + BlendArg sourceAlpha : 4; + BlendArg destColor : 4; + BlendArg destAlpha : 4; + BlendOp opColor : 4; + BlendOp opAlpha : 4; - uint8 _enabled; - uint8 _source; - uint8 _destination; - uint8 _operation; public: - BlendFunction(bool enabled, - BlendArg sourceColor, BlendOp operationColor, BlendArg destinationColor, - BlendArg sourceAlpha, BlendOp operationAlpha, BlendArg destinationAlpha) : - _enabled(enabled), - _source(sourceColor | (sourceAlpha << ALPHA_OFFSET)), - _destination(destinationColor | (destinationAlpha << ALPHA_OFFSET)), - _operation(operationColor | (operationAlpha << ALPHA_OFFSET)) {} + BlendArg sourceColor, + BlendOp operationColor, + BlendArg destinationColor, + BlendArg sourceAlpha, + BlendOp operationAlpha, + BlendArg destinationAlpha) : + enabled(enabled), + sourceColor(sourceColor), sourceAlpha(sourceAlpha), + destColor(destinationColor), destAlpha(destinationAlpha), + opColor(operationColor), opAlpha(operationAlpha) {} - BlendFunction(bool enabled, BlendArg source = ONE, BlendOp operation = BLEND_OP_ADD, BlendArg destination = ZERO) : - _enabled(enabled), - _source(source | (source << ALPHA_OFFSET)), - _destination(destination | (destination << ALPHA_OFFSET)), - _operation(operation | (operation << ALPHA_OFFSET)) {} + BlendFunction(bool enabled = false, BlendArg source = ONE, BlendOp operation = BLEND_OP_ADD, BlendArg destination = ZERO) : + BlendFunction(enabled, source, operation, destination, source, operation, destination) {} - bool isEnabled() const { return (_enabled != 0); } + bool isEnabled() const { return (enabled != 0); } - BlendArg getSourceColor() const { return BlendArg(_source & COLOR_MASK); } - BlendArg getDestinationColor() const { return BlendArg(_destination & COLOR_MASK); } - BlendOp getOperationColor() const { return BlendOp(_operation & COLOR_MASK); } + BlendArg getSourceColor() const { return sourceColor; } + BlendArg getDestinationColor() const { return destColor; } + BlendOp getOperationColor() const { return opColor; } - BlendArg getSourceAlpha() const { return BlendArg((_source & ALPHA_MASK) >> ALPHA_OFFSET); } - BlendArg getDestinationAlpha() const { return BlendArg((_destination & ALPHA_MASK) >> ALPHA_OFFSET); } - BlendOp getOperationAlpha() const { return BlendOp((_operation & ALPHA_MASK) >> ALPHA_OFFSET); } + BlendArg getSourceAlpha() const { return sourceAlpha; } + BlendArg getDestinationAlpha() const { return destAlpha; } + BlendOp getOperationAlpha() const { return opAlpha; } int32 getRaw() const { return *(reinterpret_cast(this)); } BlendFunction(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const BlendFunction& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const BlendFunction& right) const { return getRaw() != right.getRaw(); } + bool operator==(const BlendFunction& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const BlendFunction& right) const { return getRaw() != right.getRaw(); } }; + static_assert(sizeof(BlendFunction) == sizeof(uint32_t), "BlendFunction size check"); + + struct Flags { + Flags() : + frontFaceClockwise(false), depthClampEnable(false), scissorEnable(false), multisampleEnable(false), + antialisedLineEnable(true), alphaToCoverageEnable(false), _spare1(0) {} + bool frontFaceClockwise : 1; + bool depthClampEnable : 1; + bool scissorEnable : 1; + bool multisampleEnable : 1; + bool antialisedLineEnable : 1; + bool alphaToCoverageEnable : 1; + uint8 _spare1 : 2; + + bool operator==(const Flags& right) const { return *(uint8*)this == *(uint8*)&right; } + bool operator!=(const Flags& right) const { return *(uint8*)this != *(uint8*)&right; } + }; + + static_assert(sizeof(Flags) == sizeof(uint8), "Flags size check"); + // The Data class is the full explicit description of the State class fields value. // Useful for having one const static called Default for reference or for the gpu::Backend to keep track of the current value class Data { @@ -237,71 +269,72 @@ public: float depthBias = 0.0f; float depthBiasSlopeScale = 0.0f; - DepthTest depthTest = DepthTest(false, true, LESS); - - StencilActivation stencilActivation = StencilActivation(false); - StencilTest stencilTestFront = StencilTest(0, 0xff, ALWAYS, STENCIL_OP_KEEP, STENCIL_OP_KEEP, STENCIL_OP_KEEP); - StencilTest stencilTestBack = StencilTest(0, 0xff, ALWAYS, STENCIL_OP_KEEP, STENCIL_OP_KEEP, STENCIL_OP_KEEP); - + DepthTest depthTest; + StencilActivation stencilActivation; + StencilTest stencilTestFront; + StencilTest stencilTestBack; uint32 sampleMask = 0xFFFFFFFF; + BlendFunction blendFunction; + FillMode fillMode{ FILL_FACE }; + CullMode cullMode{ CULL_NONE }; + ColorMask colorWriteMask{ WRITE_ALL }; - BlendFunction blendFunction = BlendFunction(false); - - uint8 fillMode = FILL_FACE; - uint8 cullMode = CULL_NONE; - - uint8 colorWriteMask = WRITE_ALL; - - bool frontFaceClockwise : 1; - bool depthClampEnable : 1; - bool scissorEnable : 1; - bool multisampleEnable : 1; - bool antialisedLineEnable : 1; - bool alphaToCoverageEnable : 1; - - Data() : - frontFaceClockwise(false), - depthClampEnable(false), - scissorEnable(false), - multisampleEnable(false), - antialisedLineEnable(true), - alphaToCoverageEnable(false) - {} + Flags flags; }; + static_assert(offsetof(Data, depthBias) == 0, "Data offsets"); + static_assert(offsetof(Data, depthBiasSlopeScale) == 4, "Data offsets"); + static_assert(offsetof(Data, depthTest) == 8, "Data offsets"); + static_assert(offsetof(Data, stencilActivation) == 12, "Data offsets"); + static_assert(offsetof(Data, stencilTestFront) == 16, "Data offsets"); + static_assert(offsetof(Data, stencilTestBack) == 20, "Data offsets"); + static_assert(offsetof(Data, sampleMask) == 24, "Data offsets"); + static_assert(offsetof(Data, blendFunction) == 28, "Data offsets"); + static_assert(offsetof(Data, fillMode) == 32, "Data offsets"); + static_assert(offsetof(Data, cullMode) == 33, "Data offsets"); + static_assert(offsetof(Data, colorWriteMask) == 34, "Data offsets"); + static_assert(offsetof(Data, flags) == 35, "Data offsets"); + static_assert(sizeof(Data) == 36, "Data Size Check"); + + std::string getKey() const; + // The unique default values for all the fields static const Data DEFAULT; - void setFillMode(FillMode fill) { SET_FIELD(FILL_MODE, DEFAULT.fillMode, fill, _values.fillMode); } - FillMode getFillMode() const { return FillMode(_values.fillMode); } + void setFillMode(FillMode fill) { SET_FIELD(FILL_MODE, fillMode, fill); } + FillMode getFillMode() const { return _values.fillMode; } - void setCullMode(CullMode cull) { SET_FIELD(CULL_MODE, DEFAULT.cullMode, cull, _values.cullMode); } - CullMode getCullMode() const { return CullMode(_values.cullMode); } + void setCullMode(CullMode cull) { SET_FIELD(CULL_MODE, cullMode, cull); } + CullMode getCullMode() const { return _values.cullMode; } - void setFrontFaceClockwise(bool isClockwise) { SET_FIELD(FRONT_FACE_CLOCKWISE, DEFAULT.frontFaceClockwise, isClockwise, _values.frontFaceClockwise); } - bool isFrontFaceClockwise() const { return _values.frontFaceClockwise; } + const Flags& getFlags() const { return _values.flags; } - void setDepthClampEnable(bool enable) { SET_FIELD(DEPTH_CLAMP_ENABLE, DEFAULT.depthClampEnable, enable, _values.depthClampEnable); } - bool isDepthClampEnable() const { return _values.depthClampEnable; } + void setFrontFaceClockwise(bool isClockwise) { SET_FIELD(FRONT_FACE_CLOCKWISE, flags.frontFaceClockwise, isClockwise); } + bool isFrontFaceClockwise() const { return _values.flags.frontFaceClockwise; } - void setScissorEnable(bool enable) { SET_FIELD(SCISSOR_ENABLE, DEFAULT.scissorEnable, enable, _values.scissorEnable); } - bool isScissorEnable() const { return _values.scissorEnable; } + void setDepthClampEnable(bool enable) { SET_FIELD(DEPTH_CLAMP_ENABLE, flags.depthClampEnable, enable); } + bool isDepthClampEnable() const { return _values.flags.depthClampEnable; } - void setMultisampleEnable(bool enable) { SET_FIELD(MULTISAMPLE_ENABLE, DEFAULT.multisampleEnable, enable, _values.multisampleEnable); } - bool isMultisampleEnable() const { return _values.multisampleEnable; } + void setScissorEnable(bool enable) { SET_FIELD(SCISSOR_ENABLE, flags.scissorEnable, enable); } + bool isScissorEnable() const { return _values.flags.scissorEnable; } - void setAntialiasedLineEnable(bool enable) { SET_FIELD(ANTIALISED_LINE_ENABLE, DEFAULT.antialisedLineEnable, enable, _values.antialisedLineEnable); } - bool isAntialiasedLineEnable() const { return _values.antialisedLineEnable; } + void setMultisampleEnable(bool enable) { SET_FIELD(MULTISAMPLE_ENABLE, flags.multisampleEnable, enable); } + bool isMultisampleEnable() const { return _values.flags.multisampleEnable; } + + void setAntialiasedLineEnable(bool enable) { SET_FIELD(ANTIALISED_LINE_ENABLE, flags.antialisedLineEnable, enable); } + bool isAntialiasedLineEnable() const { return _values.flags.antialisedLineEnable; } // Depth Bias - void setDepthBias(float bias) { SET_FIELD(DEPTH_BIAS, DEFAULT.depthBias, bias, _values.depthBias); } + void setDepthBias(float bias) { SET_FIELD(DEPTH_BIAS, depthBias, bias); } float getDepthBias() const { return _values.depthBias; } - void setDepthBiasSlopeScale(float scale) { SET_FIELD(DEPTH_BIAS_SLOPE_SCALE, DEFAULT.depthBiasSlopeScale, scale, _values.depthBiasSlopeScale); } + void setDepthBiasSlopeScale(float scale) { SET_FIELD(DEPTH_BIAS_SLOPE_SCALE, depthBiasSlopeScale, scale); } float getDepthBiasSlopeScale() const { return _values.depthBiasSlopeScale; } // Depth Test - void setDepthTest(DepthTest depthTest) { SET_FIELD(DEPTH_TEST, DEFAULT.depthTest, depthTest, _values.depthTest); } - void setDepthTest(bool enable, bool writeMask, ComparisonFunction func) { setDepthTest(DepthTest(enable, writeMask, func)); } + void setDepthTest(DepthTest newDepthTest) { SET_FIELD(DEPTH_TEST, depthTest, newDepthTest); } + void setDepthTest(bool enable, bool writeMask, ComparisonFunction func) { + setDepthTest(DepthTest(enable, writeMask, func)); + } DepthTest getDepthTest() const { return _values.depthTest; } bool isDepthTestEnabled() const { return getDepthTest().isEnabled(); } @@ -310,11 +343,14 @@ public: // Stencil test void setStencilTest(bool enabled, uint8 frontWriteMask, StencilTest frontTest, uint8 backWriteMask, StencilTest backTest) { - SET_FIELD(STENCIL_ACTIVATION, DEFAULT.stencilActivation, StencilActivation(enabled, frontWriteMask, backWriteMask), _values.stencilActivation); - SET_FIELD(STENCIL_TEST_FRONT, DEFAULT.stencilTestFront, frontTest, _values.stencilTestFront); - SET_FIELD(STENCIL_TEST_BACK, DEFAULT.stencilTestBack, backTest, _values.stencilTestBack); } + SET_FIELD(STENCIL_ACTIVATION, stencilActivation, StencilActivation(enabled, frontWriteMask, backWriteMask)); + SET_FIELD(STENCIL_TEST_FRONT, stencilTestFront, frontTest); + SET_FIELD(STENCIL_TEST_BACK, stencilTestBack, backTest); + } + void setStencilTest(bool enabled, uint8 frontWriteMask, StencilTest frontTest) { - setStencilTest(enabled, frontWriteMask, frontTest, frontWriteMask, frontTest); } + setStencilTest(enabled, frontWriteMask, frontTest, frontWriteMask, frontTest); + } StencilActivation getStencilActivation() const { return _values.stencilActivation; } StencilTest getStencilTestFront() const { return _values.stencilTestFront; } @@ -325,32 +361,45 @@ public: uint8 getStencilWriteMaskBack() const { return getStencilActivation().getWriteMaskBack(); } // Alpha to coverage - void setAlphaToCoverageEnable(bool enable) { SET_FIELD(ALPHA_TO_COVERAGE_ENABLE, DEFAULT.alphaToCoverageEnable, enable, _values.alphaToCoverageEnable); } - bool isAlphaToCoverageEnabled() const { return _values.alphaToCoverageEnable; } + void setAlphaToCoverageEnable(bool enable) { SET_FIELD(ALPHA_TO_COVERAGE_ENABLE, flags.alphaToCoverageEnable, enable); } + bool isAlphaToCoverageEnabled() const { return _values.flags.alphaToCoverageEnable; } // Sample mask - void setSampleMask(uint32 mask) { SET_FIELD(SAMPLE_MASK, DEFAULT.sampleMask, mask, _values.sampleMask); } + void setSampleMask(uint32 mask) { SET_FIELD(SAMPLE_MASK, sampleMask, mask); } uint32 getSampleMask() const { return _values.sampleMask; } // Blend Function - void setBlendFunction(BlendFunction function) { SET_FIELD(BLEND_FUNCTION, DEFAULT.blendFunction, function, _values.blendFunction); } - BlendFunction getBlendFunction() const { return _values.blendFunction; } + void setBlendFunction(BlendFunction function) { SET_FIELD(BLEND_FUNCTION, blendFunction, function); } + const BlendFunction& getBlendFunction() const { return _values.blendFunction; } - void setBlendFunction(bool enabled, BlendArg sourceColor, BlendOp operationColor, BlendArg destinationColor, BlendArg sourceAlpha, BlendOp operationAlpha, BlendArg destinationAlpha) { - setBlendFunction(BlendFunction(enabled, sourceColor, operationColor, destinationColor, sourceAlpha, operationAlpha, destinationAlpha)); } + void setBlendFunction(bool enabled, + BlendArg sourceColor, + BlendOp operationColor, + BlendArg destinationColor, + BlendArg sourceAlpha, + BlendOp operationAlpha, + BlendArg destinationAlpha) { + setBlendFunction(BlendFunction(enabled, sourceColor, operationColor, destinationColor, sourceAlpha, operationAlpha, + destinationAlpha)); + } void setBlendFunction(bool enabled, BlendArg source, BlendOp operation, BlendArg destination) { - setBlendFunction(BlendFunction(enabled, source, operation, destination)); } + setBlendFunction(BlendFunction(enabled, source, operation, destination)); + } bool isBlendEnabled() const { return getBlendFunction().isEnabled(); } // Color write mask - void setColorWriteMask(uint8 mask) { SET_FIELD(COLOR_WRITE_MASK, DEFAULT.colorWriteMask, mask, _values.colorWriteMask); } - void setColorWriteMask(bool red, bool green, bool blue, bool alpha) { uint32 value = ((WRITE_RED * red) | (WRITE_GREEN * green) | (WRITE_BLUE * blue) | (WRITE_ALPHA * alpha)); SET_FIELD(COLOR_WRITE_MASK, DEFAULT.colorWriteMask, value, _values.colorWriteMask); } - uint8 getColorWriteMask() const { return _values.colorWriteMask; } + void setColorWriteMask(ColorMask mask) { SET_FIELD(COLOR_WRITE_MASK, colorWriteMask, mask); } + void setColorWriteMask(bool red, bool green, bool blue, bool alpha) { + ColorMask value = (ColorMask)((WRITE_RED * red) | (WRITE_GREEN * green) | (WRITE_BLUE * blue) | (WRITE_ALPHA * alpha)); + SET_FIELD(COLOR_WRITE_MASK, colorWriteMask, value); + } + ColorMask getColorWriteMask() const { return _values.colorWriteMask; } // All the possible fields // NOTE: If you change this, you must update GLBackend::GLState::_resetStateCommands - enum Field { + enum Field + { FILL_MODE, CULL_MODE, FRONT_FACE_CLOCKWISE, @@ -376,7 +425,7 @@ public: COLOR_WRITE_MASK, - NUM_FIELDS, // not a valid field, just the count + NUM_FIELDS, // not a valid field, just the count }; // The signature of the state tells which fields of the state are not default @@ -391,20 +440,20 @@ public: State(const Data& values); const Data& getValues() const { return _values; } - const GPUObjectPointer gpuObject {}; + const GPUObjectPointer gpuObject{}; protected: State(const State& state); State& operator=(const State& state); Data _values; - Signature _signature{0}; - Stamp _stamp{0}; + Signature _signature{ 0 }; + Stamp _stamp{ 0 }; }; -typedef std::shared_ptr< State > StatePointer; -typedef std::vector< StatePointer > States; +typedef std::shared_ptr StatePointer; +typedef std::vector States; -}; +}; // namespace gpu #endif diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 2e1ed1d83c..42a0de66ca 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -128,6 +128,8 @@ public: uint32 _elementTotalSize { 0 }; std::string _key; + friend class Serializer; + friend class Deserializer; void evaluateCache(); }; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 73ed1b15dc..23bfff6873 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -189,6 +189,8 @@ public: } protected: Desc _desc; + + friend class Deserializer; }; enum class TextureUsageType : uint8 { @@ -371,6 +373,8 @@ public: ktx::KTXDescriptorPointer _ktxDescriptor; friend class Texture; + friend class Serializer; + friend class Deserializer; }; uint16 minAvailableMipLevel() const { return _storage->minAvailableMipLevel(); }; @@ -628,6 +632,9 @@ protected: static TexturePointer create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler); Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips); + + friend class Serializer; + friend class Deserializer; }; typedef std::shared_ptr TexturePointer; diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 90bd083d02..747788aef8 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -40,13 +40,13 @@ namespace scriptable { * @typedef {object} Graphics.Material * @property {string} name * @property {string} model - * @property {number} opacity - * @property {number} roughness - * @property {number} metallic - * @property {number} scattering - * @property {boolean} unlit - * @propety {Vec3} emissive - * @propety {Vec3} albedo + * @property {number|string} opacity + * @property {number|string} roughness + * @property {number|string} metallic + * @property {number|string} scattering + * @property {boolean|string} unlit + * @propety {Vec3|string} emissive + * @propety {Vec3|string} albedo * @property {string} emissiveMap * @property {string} albedoMap * @property {string} opacityMap @@ -59,6 +59,11 @@ namespace scriptable { * @property {string} occlusionMap * @property {string} lightmapMap * @property {string} scatteringMap + * @property {string} texCoordTransform0 + * @property {string} texCoordTransform1 + * @property {string} lightmapParams + * @property {string} materialParams + * @property {boolean} defaultFallthrough */ class ScriptableMaterial { public: @@ -88,6 +93,9 @@ namespace scriptable { QString occlusionMap; QString lightmapMap; QString scatteringMap; + + bool defaultFallthrough; + std::unordered_map propertyFallthroughs; // not actually exposed to script }; /**jsdoc diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index aa8e954e0a..848f9d42ac 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -362,25 +362,64 @@ namespace scriptable { QScriptValue obj = engine->newObject(); obj.setProperty("name", material.name); obj.setProperty("model", material.model); - obj.setProperty("opacity", material.opacity); - obj.setProperty("roughness", material.roughness); - obj.setProperty("metallic", material.metallic); - obj.setProperty("scattering", material.scattering); - obj.setProperty("unlit", material.unlit); - obj.setProperty("emissive", vec3ColorToScriptValue(engine, material.emissive)); - obj.setProperty("albedo", vec3ColorToScriptValue(engine, material.albedo)); - obj.setProperty("emissiveMap", material.emissiveMap); - obj.setProperty("albedoMap", material.albedoMap); + + const QScriptValue FALLTHROUGH("fallthrough"); + obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity); + obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness); + obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic); + obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering); + obj.setProperty("unlit", material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT) ? FALLTHROUGH : material.unlit); + obj.setProperty("emissive", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.emissive)); + obj.setProperty("albedo", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.albedo)); + + obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap); + obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap); obj.setProperty("opacityMap", material.opacityMap); - obj.setProperty("metallicMap", material.metallicMap); - obj.setProperty("specularMap", material.specularMap); - obj.setProperty("roughnessMap", material.roughnessMap); - obj.setProperty("glossMap", material.glossMap); - obj.setProperty("normalMap", material.normalMap); - obj.setProperty("bumpMap", material.bumpMap); - obj.setProperty("occlusionMap", material.occlusionMap); - obj.setProperty("lightmapMap", material.lightmapMap); - obj.setProperty("scatteringMap", material.scatteringMap); + obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap); + obj.setProperty("lightmapMap", material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT) ? FALLTHROUGH : material.lightmapMap); + obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap); + + // Only set one of each of these + if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) { + obj.setProperty("metallicMap", FALLTHROUGH); + } else if (!material.metallicMap.isEmpty()) { + obj.setProperty("metallicMap", material.metallicMap); + } else if (!material.specularMap.isEmpty()) { + obj.setProperty("specularMap", material.specularMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT)) { + obj.setProperty("roughnessMap", FALLTHROUGH); + } else if (!material.roughnessMap.isEmpty()) { + obj.setProperty("roughnessMap", material.roughnessMap); + } else if (!material.glossMap.isEmpty()) { + obj.setProperty("glossMap", material.glossMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT)) { + obj.setProperty("normalMap", FALLTHROUGH); + } else if (!material.normalMap.isEmpty()) { + obj.setProperty("normalMap", material.normalMap); + } else if (!material.bumpMap.isEmpty()) { + obj.setProperty("bumpMap", material.bumpMap); + } + + // These need to be implemented, but set the fallthrough for now + if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) { + obj.setProperty("texCoordTransform0", FALLTHROUGH); + } + if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) { + obj.setProperty("texCoordTransform1", FALLTHROUGH); + } + if (material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) { + obj.setProperty("lightmapParams", FALLTHROUGH); + } + if (material.propertyFallthroughs.at(graphics::Material::MATERIAL_PARAMS)) { + obj.setProperty("materialParams", FALLTHROUGH); + } + + obj.setProperty("defaultFallthrough", material.defaultFallthrough); + return obj; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 51085561c3..4ff751782c 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -42,6 +42,9 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const lightmapMap = material.lightmapMap; scatteringMap = material.scatteringMap; + defaultFallthrough = material.defaultFallthrough; + propertyFallthroughs = material.propertyFallthroughs; + return *this; } @@ -54,7 +57,9 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint scattering(material->getScattering()), unlit(material->isUnlit()), emissive(material->getEmissive()), - albedo(material->getAlbedo()) + albedo(material->getAlbedo()), + defaultFallthrough(material->getDefaultFallthrough()), + propertyFallthroughs(material->getPropertyFallthroughs()) { auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); if (map && map->getTextureSource()) { diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index be99cea681..7befb7e053 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -17,125 +17,125 @@ using namespace graphics; using namespace gpu; -Material::Material() : - _key(0), - _schemaBuffer(), - _textureMaps() -{ - // created from nothing: create the Buffer to store the properties - Schema schema; - _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); +const float Material::DEFAULT_EMISSIVE { 0.0f }; +const float Material::DEFAULT_OPACITY { 1.0f }; +const float Material::DEFAULT_ALBEDO { 0.5f }; +const float Material::DEFAULT_METALLIC { 0.0f }; +const float Material::DEFAULT_ROUGHNESS { 1.0f }; +const float Material::DEFAULT_SCATTERING { 0.0f }; + +Material::Material() { + for (int i = 0; i < NUM_TOTAL_FLAGS; i++) { + _propertyFallthroughs[i] = false; + } } Material::Material(const Material& material) : _name(material._name), + _model(material._model), _key(material._key), - _textureMaps(material._textureMaps) + _emissive(material._emissive), + _opacity(material._opacity), + _albedo(material._albedo), + _roughness(material._roughness), + _metallic(material._metallic), + _scattering(material._scattering), + _texcoordTransforms(material._texcoordTransforms), + _lightmapParams(material._lightmapParams), + _materialParams(material._materialParams), + _textureMaps(material._textureMaps), + _defaultFallthrough(material._defaultFallthrough), + _propertyFallthroughs(material._propertyFallthroughs) { - // copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer - Schema schema; - _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); - _schemaBuffer.edit() = material._schemaBuffer.get(); } -Material& Material::operator= (const Material& material) { +Material& Material::operator=(const Material& material) { QMutexLocker locker(&_textureMapsMutex); _name = material._name; + _model = material._model; + _key = material._key; + _emissive = material._emissive; + _opacity = material._opacity; + _albedo = material._albedo; + _roughness = material._roughness; + _metallic = material._metallic; + _scattering = material._scattering; + _texcoordTransforms = material._texcoordTransforms; + _lightmapParams = material._lightmapParams; + _materialParams = material._materialParams; + _textureMaps = material._textureMaps; - _key = (material._key); - _textureMaps = (material._textureMaps); - _hasCalculatedTextureInfo = false; - - // copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer - Schema schema; - _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); - _schemaBuffer.edit() = material._schemaBuffer.get(); + _defaultFallthrough = material._defaultFallthrough; + _propertyFallthroughs = material._propertyFallthroughs; return (*this); } -Material::~Material() { -} - -void Material::setEmissive(const Color& emissive, bool isSRGB) { - _key.setEmissive(glm::any(glm::greaterThan(emissive, Color(0.0f)))); - _schemaBuffer.edit()._key = (uint32) _key._flags.to_ulong(); - _schemaBuffer.edit()._emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive); +void Material::setEmissive(const glm::vec3& emissive, bool isSRGB) { + _key.setEmissive(glm::any(glm::greaterThan(emissive, glm::vec3(0.0f)))); + _emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive); } void Material::setOpacity(float opacity) { _key.setTranslucentFactor((opacity < 1.0f)); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._opacity = opacity; + _opacity = opacity; } void Material::setUnlit(bool value) { _key.setUnlit(value); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); } -void Material::setAlbedo(const Color& albedo, bool isSRGB) { - _key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f)))); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); +void Material::setAlbedo(const glm::vec3& albedo, bool isSRGB) { + _key.setAlbedo(glm::any(glm::greaterThan(albedo, glm::vec3(0.0f)))); + _albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); } void Material::setRoughness(float roughness) { roughness = std::min(1.0f, std::max(roughness, 0.0f)); - _key.setGlossy((roughness < 1.0f)); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._roughness = roughness; + _key.setGlossy(roughness < 1.0f); + _roughness = roughness; } void Material::setMetallic(float metallic) { metallic = glm::clamp(metallic, 0.0f, 1.0f); _key.setMetallic(metallic > 0.0f); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._metallic = metallic; + _metallic = metallic; } void Material::setScattering(float scattering) { scattering = glm::clamp(scattering, 0.0f, 1.0f); _key.setMetallic(scattering > 0.0f); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._scattering = scattering; + _scattering = scattering; } void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { QMutexLocker locker(&_textureMapsMutex); if (textureMap) { - _key.setMapChannel(channel, (true)); + _key.setMapChannel(channel, true); _textureMaps[channel] = textureMap; } else { - _key.setMapChannel(channel, (false)); + _key.setMapChannel(channel, false); _textureMaps.erase(channel); } - _hasCalculatedTextureInfo = false; - - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); if (channel == MaterialKey::ALBEDO_MAP) { resetOpacityMap(); - - // update the texcoord0 with albedo - _schemaBuffer.edit()._texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + _texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); } if (channel == MaterialKey::OCCLUSION_MAP) { - _schemaBuffer.edit()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + _texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); } if (channel == MaterialKey::LIGHTMAP_MAP) { // update the texcoord1 with lightmap - _schemaBuffer.edit()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); - _schemaBuffer.edit()._lightmapParams = (textureMap ? glm::vec2(textureMap->getLightmapOffsetScale()) : glm::vec2(0.0, 1.0)); + _texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + _lightmapParams = (textureMap ? glm::vec2(textureMap->getLightmapOffsetScale()) : glm::vec2(0.0, 1.0)); } - _schemaBuffer.edit()._materialParams = (textureMap ? glm::vec2(textureMap->getMappingMode(), textureMap->getRepeat()) : glm::vec2(MaterialMappingMode::UV, 1.0)); - - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + _materialParams = (textureMap ? glm::vec2(textureMap->getMappingMode(), textureMap->getRepeat()) : glm::vec2(MaterialMappingMode::UV, 1.0)); } @@ -163,11 +163,8 @@ void Material::resetOpacityMap() const { } } } - - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); } - const TextureMapPointer Material::getTextureMap(MapChannel channel) const { QMutexLocker locker(&_textureMapsMutex); @@ -179,40 +176,6 @@ const TextureMapPointer Material::getTextureMap(MapChannel channel) const { } } - -bool Material::calculateMaterialInfo() const { - if (!_hasCalculatedTextureInfo) { - QMutexLocker locker(&_textureMapsMutex); - - bool allTextures = true; // assume we got this... - _textureSize = 0; - _textureCount = 0; - - for (auto const &textureMapItem : _textureMaps) { - auto textureMap = textureMapItem.second; - if (textureMap) { - auto textureSoure = textureMap->getTextureSource(); - if (textureSoure) { - auto texture = textureSoure->getGPUTexture(); - if (texture) { - auto size = texture->getSize(); - _textureSize += size; - _textureCount++; - } else { - allTextures = false; - } - } else { - allTextures = false; - } - } else { - allTextures = false; - } - } - _hasCalculatedTextureInfo = allTextures; - } - return _hasCalculatedTextureInfo; -} - void Material::setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat) { for (auto &textureMapItem : _textureMaps) { if (textureMapItem.second) { @@ -222,7 +185,32 @@ void Material::setTextureTransforms(const Transform& transform, MaterialMappingM } } for (int i = 0; i < NUM_TEXCOORD_TRANSFORMS; i++) { - _schemaBuffer.edit()._texcoordTransforms[i] = transform.getMatrix(); + _texcoordTransforms[i] = transform.getMatrix(); } - _schemaBuffer.edit()._materialParams = glm::vec2(mode, repeat); + _materialParams = glm::vec2(mode, repeat); } + +MultiMaterial::MultiMaterial() { + Schema schema; + _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); +} + +void MultiMaterial::calculateMaterialInfo() const { + if (!_hasCalculatedTextureInfo) { + bool allTextures = true; // assume we got this... + _textureSize = 0; + _textureCount = 0; + + auto textures = _textureTable->getTextures(); + for (auto const &texture : textures) { + if (texture && texture->isDefined()) { + auto size = texture->getSize(); + _textureSize += size; + _textureCount++; + } else { + allTextures = false; + } + } + _hasCalculatedTextureInfo = allTextures; + } +} \ No newline at end of file diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 9711bd9000..fdddf3640a 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -176,7 +177,6 @@ public: bool isTexelOpaque() const { return isOpaque() && isOpacityMaskMap(); } }; - class MaterialFilter { public: MaterialKey::Flags _value{ 0 }; @@ -266,84 +266,44 @@ public: class Material { public: - typedef gpu::BufferView UniformBufferView; - - typedef glm::vec3 Color; - - // Texture Map Array Schema - static const int NUM_TEXCOORD_TRANSFORMS{ 2 }; - typedef MaterialKey::MapChannel MapChannel; typedef std::map TextureMaps; - typedef std::bitset MapFlags; Material(); Material(const Material& material); Material& operator= (const Material& material); - virtual ~Material(); const MaterialKey& getKey() const { return _key; } - void setEmissive(const Color& emissive, bool isSRGB = true); - Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._emissive) : _schemaBuffer.get()._emissive); } + static const float DEFAULT_EMISSIVE; + void setEmissive(const glm::vec3& emissive, bool isSRGB = true); + glm::vec3 getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_emissive) : _emissive); } + static const float DEFAULT_OPACITY; void setOpacity(float opacity); - float getOpacity() const { return _schemaBuffer.get()._opacity; } + float getOpacity() const { return _opacity; } void setUnlit(bool value); bool isUnlit() const { return _key.isUnlit(); } - void setAlbedo(const Color& albedo, bool isSRGB = true); - Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._albedo) : _schemaBuffer.get()._albedo); } + static const float DEFAULT_ALBEDO; + void setAlbedo(const glm::vec3& albedo, bool isSRGB = true); + glm::vec3 getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_albedo) : _albedo); } + static const float DEFAULT_METALLIC; void setMetallic(float metallic); - float getMetallic() const { return _schemaBuffer.get()._metallic; } + float getMetallic() const { return _metallic; } + static const float DEFAULT_ROUGHNESS; void setRoughness(float roughness); - float getRoughness() const { return _schemaBuffer.get()._roughness; } + float getRoughness() const { return _roughness; } + static const float DEFAULT_SCATTERING; void setScattering(float scattering); - float getScattering() const { return _schemaBuffer.get()._scattering; } - - // Schema to access the attribute values of the material - class Schema { - public: - glm::vec3 _emissive { 0.0f }; // No Emissive - float _opacity { 1.0f }; // Opacity = 1 => Not Transparent - - glm::vec3 _albedo { 0.5f }; // Grey albedo => isAlbedo - float _roughness { 1.0f }; // Roughness = 1 => Not Glossy - - float _metallic { 0.0f }; // Not Metallic - float _scattering { 0.0f }; // Scattering info -#if defined(__clang__) - __attribute__((unused)) -#endif - glm::vec2 _spare { 0.0f }; // Padding - - uint32_t _key { 0 }; // a copy of the materialKey -#if defined(__clang__) - __attribute__((unused)) -#endif - glm::vec3 _spare2 { 0.0f }; - - // for alignment beauty, Material size == Mat4x4 - - // Texture Coord Transform Array - glm::mat4 _texcoordTransforms[NUM_TEXCOORD_TRANSFORMS]; - - glm::vec2 _lightmapParams { 0.0, 1.0 }; - - // x: material mode (0 for UV, 1 for PROJECTED) - // y: 1 for texture repeat, 0 for discard outside of 0 - 1 - glm::vec2 _materialParams { 0.0, 1.0 }; - - Schema() {} - }; - - const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } + float getScattering() const { return _scattering; } // The texture map to channel association + static const int NUM_TEXCOORD_TRANSFORMS { 2 }; void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap); const TextureMaps& getTextureMaps() const { return _textureMaps; } // FIXME - not thread safe... const TextureMapPointer getTextureMap(MapChannel channel) const; @@ -355,10 +315,6 @@ public: // conversion from legacy material properties to PBR equivalent static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; } - int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } - size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } - bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } - void setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat); const std::string& getName() const { return _name; } @@ -366,28 +322,50 @@ public: const std::string& getModel() const { return _model; } void setModel(const std::string& model) { _model = model; } - const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } + glm::mat4 getTexCoordTransform(uint i) const { return _texcoordTransforms[i]; } + glm::vec2 getLightmapParams() const { return _lightmapParams; } + glm::vec2 getMaterialParams() const { return _materialParams; } + + bool getDefaultFallthrough() const { return _defaultFallthrough; } + void setDefaultFallthrough(bool defaultFallthrough) { _defaultFallthrough = defaultFallthrough; } + + enum ExtraFlagBit { + TEXCOORDTRANSFORM0 = MaterialKey::NUM_FLAGS, + TEXCOORDTRANSFORM1, + LIGHTMAP_PARAMS, + MATERIAL_PARAMS, + + NUM_TOTAL_FLAGS + }; + std::unordered_map getPropertyFallthroughs() { return _propertyFallthroughs; } + bool getPropertyFallthrough(uint property) { return _propertyFallthroughs[property]; } + void setPropertyDoesFallthrough(uint property) { _propertyFallthroughs[property] = true; } protected: std::string _name { "" }; private: - mutable MaterialKey _key; - mutable UniformBufferView _schemaBuffer; - mutable gpu::TextureTablePointer _textureTable{ std::make_shared() }; + std::string _model { "hifi_pbr" }; + mutable MaterialKey _key { 0 }; + // Material properties + glm::vec3 _emissive { DEFAULT_EMISSIVE }; + float _opacity { DEFAULT_OPACITY }; + glm::vec3 _albedo { DEFAULT_ALBEDO }; + float _roughness { DEFAULT_ROUGHNESS }; + float _metallic { DEFAULT_METALLIC }; + float _scattering { DEFAULT_SCATTERING }; + std::array _texcoordTransforms; + glm::vec2 _lightmapParams { 0.0, 1.0 }; + glm::vec2 _materialParams { 0.0, 1.0 }; TextureMaps _textureMaps; + bool _defaultFallthrough { false }; + std::unordered_map _propertyFallthroughs { NUM_TOTAL_FLAGS }; + mutable QMutex _textureMapsMutex { QMutex::Recursive }; - mutable size_t _textureSize { 0 }; - mutable int _textureCount { 0 }; - mutable bool _hasCalculatedTextureInfo { false }; - bool calculateMaterialInfo() const; - - std::string _model { "hifi_pbr" }; - }; -typedef std::shared_ptr< Material > MaterialPointer; +typedef std::shared_ptr MaterialPointer; class MaterialLayer { public: @@ -403,9 +381,18 @@ public: return left.priority < right.priority; } }; +typedef std::priority_queue, MaterialLayerCompare> MaterialLayerQueue; -class MultiMaterial : public std::priority_queue, MaterialLayerCompare> { +class MultiMaterial : public MaterialLayerQueue { public: + MultiMaterial(); + + void push(const MaterialLayer& value) { + MaterialLayerQueue::push(value); + _hasCalculatedTextureInfo = false; + _needsUpdate = true; + } + bool remove(const MaterialPointer& value) { auto it = c.begin(); while (it != c.end()) { @@ -417,11 +404,78 @@ public: if (it != c.end()) { c.erase(it); std::make_heap(c.begin(), c.end(), comp); + _hasCalculatedTextureInfo = false; + _needsUpdate = true; return true; } else { return false; } } + + // Schema to access the attribute values of the material + class Schema { + public: + glm::vec3 _emissive { Material::DEFAULT_EMISSIVE }; // No Emissive + float _opacity { Material::DEFAULT_OPACITY }; // Opacity = 1 => Not Transparent + + glm::vec3 _albedo { Material::DEFAULT_ALBEDO }; // Grey albedo => isAlbedo + float _roughness { Material::DEFAULT_ROUGHNESS }; // Roughness = 1 => Not Glossy + + float _metallic { Material::DEFAULT_METALLIC }; // Not Metallic + float _scattering { Material::DEFAULT_SCATTERING }; // Scattering info +#if defined(__clang__) + __attribute__((unused)) +#endif + glm::vec2 _spare { 0.0f }; // Padding + + uint32_t _key { 0 }; // a copy of the materialKey +#if defined(__clang__) + __attribute__((unused)) +#endif + glm::vec3 _spare2 { 0.0f }; + + // for alignment beauty, Material size == Mat4x4 + + // Texture Coord Transform Array + glm::mat4 _texcoordTransforms[Material::NUM_TEXCOORD_TRANSFORMS]; + + glm::vec2 _lightmapParams { 0.0, 1.0 }; + + // x: material mode (0 for UV, 1 for PROJECTED) + // y: 1 for texture repeat, 0 for discard outside of 0 - 1 + glm::vec2 _materialParams { 0.0, 1.0 }; + + Schema() { + for (auto& transform : _texcoordTransforms) { + transform = glm::mat4(); + } + } + }; + + gpu::BufferView& getSchemaBuffer() { return _schemaBuffer; } + graphics::MaterialKey getMaterialKey() const { return graphics::MaterialKey(_schemaBuffer.get()._key); } + const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } + + bool needsUpdate() const { return _needsUpdate; } + void setNeedsUpdate(bool needsUpdate) { _needsUpdate = needsUpdate; } + + void setTexturesLoading(bool value) { _texturesLoading = value; } + bool areTexturesLoading() const { return _texturesLoading; } + + int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } + size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } + bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } + +private: + gpu::BufferView _schemaBuffer; + gpu::TextureTablePointer _textureTable { std::make_shared() }; + bool _needsUpdate { false }; + bool _texturesLoading { false }; + + mutable size_t _textureSize { 0 }; + mutable int _textureCount { 0 }; + mutable bool _hasCalculatedTextureInfo { false }; + void calculateMaterialInfo() const; }; }; diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index b9e630456d..f0fc97c5c7 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -67,108 +67,6 @@ bool HFMMaterial::needTangentSpace() const { return !normalTexture.isNull(); } -static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape); - -void HFMMesh::createBlendShapeTangents(bool generateTangents) { - for (auto& blendShape : blendshapes) { - _createBlendShapeTangents(*this, generateTangents, blendShape); - } -} - -using IndexAccessor = std::function; - -static void setTangents(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, - const QVector& vertices, const QVector& normals, QVector& tangents) { - glm::vec3 vertex[2]; - glm::vec3 normal; - glm::vec3* tangent = vertexAccessor(mesh, firstIndex, secondIndex, vertex, normal); - if (tangent) { - glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); - if (glm::length(bitangent) < EPSILON) { - return; - } - glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); - glm::vec3 normalizedNormal = glm::normalize(normal); - *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * - glm::normalize(bitangent), normalizedNormal); - } -} - -static void createTangents(const HFMMesh& mesh, bool generateFromTexCoords, - const QVector& vertices, const QVector& normals, QVector& tangents, - IndexAccessor accessor) { - // if we have a normal map (and texture coordinates), we must compute tangents - if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { - tangents.resize(vertices.size()); - - foreach(const HFMMeshPart& part, mesh.parts) { - for (int i = 0; i < part.quadIndices.size(); i += 4) { - setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i), vertices, normals, tangents); - } - // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 - // This is most likely evidence of a further problem in extractMesh() - for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { - setTangents(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1), vertices, normals, tangents); - setTangents(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2), vertices, normals, tangents); - setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); - } - if ((part.triangleIndices.size() % 3) != 0) { - qCDebug(modelformat) << "Error in extractHFMModel part.triangleIndices.size() is not divisible by three "; - } - } - } -} - -void HFMMesh::createMeshTangents(bool generateFromTexCoords) { - HFMMesh& mesh = *this; - // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't - // const in the lambda function. - auto& tangents = mesh.tangents; - createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, - [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { - outVertices[0] = mesh.vertices[firstIndex]; - outVertices[1] = mesh.vertices[secondIndex]; - outNormal = mesh.normals[firstIndex]; - return &(tangents[firstIndex]); - }); -} - -static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape) { - // Create lookup to get index in blend shape from vertex index in mesh - std::vector reverseIndices; - reverseIndices.resize(mesh.vertices.size()); - std::iota(reverseIndices.begin(), reverseIndices.end(), 0); - - for (int indexInBlendShape = 0; indexInBlendShape < blendShape.indices.size(); ++indexInBlendShape) { - auto indexInMesh = blendShape.indices[indexInBlendShape]; - reverseIndices[indexInMesh] = indexInBlendShape; - } - - createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, - [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { - const auto index1 = reverseIndices[firstIndex]; - const auto index2 = reverseIndices[secondIndex]; - - if (index1 < blendShape.vertices.size()) { - outVertices[0] = blendShape.vertices[index1]; - if (index2 < blendShape.vertices.size()) { - outVertices[1] = blendShape.vertices[index2]; - } else { - // Index isn't in the blend shape so return vertex from mesh - outVertices[1] = mesh.vertices[secondIndex]; - } - outNormal = blendShape.normals[index1]; - return &blendShape.tangents[index1]; - } else { - // Index isn't in blend shape so return nullptr - return (glm::vec3*)nullptr; - } - }); -} - QStringList HFMModel::getJointNames() const { QStringList names; foreach (const HFMJoint& joint, joints) { @@ -205,6 +103,8 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const { auto checkEachPrimitive = [=](HFMMesh& mesh, QVector indices, int primitiveSize) -> bool { // Check whether the point is "behind" all the primitives. + // But first must transform from model-frame into mesh-frame + glm::vec3 transformedPoint = glm::vec3(glm::inverse(mesh.modelTransform) * glm::vec4(point, 1.0f)); int verticesSize = mesh.vertices.size(); for (int j = 0; j < indices.size() - 2; // -2 in case the vertices aren't the right size -- we access j + 2 below @@ -212,7 +112,7 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const { if (indices[j] < verticesSize && indices[j + 1] < verticesSize && indices[j + 2] < verticesSize && - !isPointBehindTrianglesPlane(point, + !isPointBehindTrianglesPlane(transformedPoint, mesh.vertices[indices[j]], mesh.vertices[indices[j + 1]], mesh.vertices[indices[j + 2]])) { diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 78f608d72e..1bd87332a1 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -239,9 +239,6 @@ public: graphics::MeshPointer _mesh; bool wasCompressed { false }; - - void createMeshTangents(bool generateFromTexCoords); - void createBlendShapeTangents(bool generateTangents); }; /**jsdoc diff --git a/libraries/model-baker/CMakeLists.txt b/libraries/model-baker/CMakeLists.txt index 50b113976f..6fa7c1815a 100644 --- a/libraries/model-baker/CMakeLists.txt +++ b/libraries/model-baker/CMakeLists.txt @@ -1,6 +1,4 @@ set(TARGET_NAME model-baker) setup_hifi_library() -link_hifi_libraries(shared task gpu graphics) - -include_hifi_library_headers(hfm) +link_hifi_libraries(shared task gpu graphics hfm) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index bf2b21993c..8d1a82518d 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -15,26 +15,65 @@ #include "BakerTypes.h" #include "BuildGraphicsMeshTask.h" +#include "CalculateMeshNormalsTask.h" +#include "CalculateMeshTangentsTask.h" +#include "CalculateBlendshapeNormalsTask.h" +#include "CalculateBlendshapeTangentsTask.h" namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet3, hifi::URL, MeshIndicesToModelNames>; + using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { - auto& hfmModelIn = input; + const auto& hfmModelIn = input; output.edit0() = hfmModelIn->meshes.toStdVector(); output.edit1() = hfmModelIn->originalURL; output.edit2() = hfmModelIn->meshIndicesToModelNames; + auto& blendshapesPerMesh = output.edit3(); + blendshapesPerMesh.reserve(hfmModelIn->meshes.size()); + for (int i = 0; i < hfmModelIn->meshes.size(); i++) { + blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); + } + output.edit4() = hfmModelIn->materials; + } + }; + + class BuildBlendshapesTask { + public: + using Input = VaryingSet3, std::vector>; + using Output = BlendshapesPerMesh; + using JobModel = Job::ModelIO; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMeshIn = input.get0(); + const auto& normalsPerBlendshapePerMesh = input.get1(); + const auto& tangentsPerBlendshapePerMesh = input.get2(); + auto& blendshapesPerMeshOut = output; + + blendshapesPerMeshOut = blendshapesPerMeshIn; + + for (int i = 0; i < (int)blendshapesPerMeshOut.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i]; + auto& blendshapesOut = blendshapesPerMeshOut[i]; + for (int j = 0; j < (int)blendshapesOut.size(); j++) { + const auto& normals = normalsPerBlendshape[j]; + const auto& tangents = tangentsPerBlendshape[j]; + auto& blendshape = blendshapesOut[j]; + blendshape.normals = QVector::fromStdVector(normals); + blendshape.tangents = QVector::fromStdVector(tangents); + } + } } }; class BuildMeshesTask { public: - using Input = VaryingSet4, std::vector, TangentsPerMesh, BlendshapesPerMesh>; + using Input = VaryingSet5, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; using Output = std::vector; using JobModel = Job::ModelIO; @@ -42,13 +81,15 @@ namespace baker { auto& meshesIn = input.get0(); int numMeshes = (int)meshesIn.size(); auto& graphicsMeshesIn = input.get1(); - auto& tangentsPerMeshIn = input.get2(); - auto& blendshapesPerMeshIn = input.get3(); + auto& normalsPerMeshIn = input.get2(); + auto& tangentsPerMeshIn = input.get3(); + auto& blendshapesPerMeshIn = input.get4(); auto meshesOut = meshesIn; for (int i = 0; i < numMeshes; i++) { auto& meshOut = meshesOut[i]; meshOut._mesh = graphicsMeshesIn[i]; + meshOut.normals = QVector::fromStdVector(normalsPerMeshIn[i]); meshOut.tangents = QVector::fromStdVector(tangentsPerMeshIn[i]); meshOut.blendshapes = QVector::fromStdVector(blendshapesPerMeshIn[i]); } @@ -80,17 +121,27 @@ namespace baker { const auto meshesIn = modelPartsIn.getN(0); const auto url = modelPartsIn.getN(1); const auto meshIndicesToModelNames = modelPartsIn.getN(2); + const auto blendshapesPerMeshIn = modelPartsIn.getN(3); + const auto materials = modelPartsIn.getN(4); + + // Calculate normals and tangents for meshes and blendshapes if they do not exist + // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. + const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); + const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, materials).asVarying(); + const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); + const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying(); + const auto normalsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs); + const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, materials).asVarying(); + const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); // Build the graphics::MeshPointer for each hfm::Mesh - const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames).asVarying(); - const auto buildGraphicsMeshOutputs = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); - const auto graphicsMeshes = buildGraphicsMeshOutputs.getN(0); - // TODO: Move tangent/blendshape validation/calculation to an earlier step - const auto tangentsPerMesh = buildGraphicsMeshOutputs.getN(1); - const auto blendshapesPerMesh = buildGraphicsMeshOutputs.getN(2); + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); // Combine the outputs into a new hfm::Model - const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, tangentsPerMesh, blendshapesPerMesh).asVarying(); + const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); + const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); + const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying(); hfmModelOut = model.addJob("BuildModel", buildModelInputs); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 8615c1b17c..5d14ee5420 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -15,10 +15,25 @@ #include namespace baker { + using MeshIndices = std::vector; + using IndicesPerMesh = std::vector>; + using VerticesPerMesh = std::vector>; + using MeshNormals = std::vector; + using NormalsPerMesh = std::vector>; using MeshTangents = std::vector; using TangentsPerMesh = std::vector>; + using Blendshapes = std::vector; using BlendshapesPerMesh = std::vector>; + using BlendshapeVertices = std::vector; + using BlendshapeNormals = std::vector; + using BlendshapeIndices = std::vector; + using VerticesPerBlendshape = std::vector>; + using NormalsPerBlendshape = std::vector>; + using IndicesPerBlendshape = std::vector>; + using BlendshapeTangents = std::vector; + using TangentsPerBlendshape = std::vector>; + using MeshIndicesToModelNames = QHash; }; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 6d351a99e9..370add2c2e 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -26,9 +26,18 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, baker::MeshTangents& meshTangents, baker::Blendshapes& blendshapes) { +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn) { auto graphicsMesh = std::make_shared(); + // Fill tangents with a dummy value to force tangents to be present if there are normals + baker::MeshTangents meshTangents; + if (!meshTangentsIn.empty()) { + meshTangents = meshTangentsIn; + } else { + meshTangents.reserve(meshNormals.size()); + std::fill_n(std::back_inserter(meshTangents), meshNormals.size(), Vectors::UNIT_X); + } + unsigned int totalSourceIndices = 0; foreach(const HFMMeshPart& part, hfmMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); @@ -48,23 +57,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics int numVerts = hfmMesh.vertices.size(); - if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - meshTangents.reserve(hfmMesh.normals.size()); - std::fill_n(std::back_inserter(meshTangents), hfmMesh.normals.size(), Vectors::UNIT_X); - } else { - meshTangents = hfmMesh.tangents.toStdVector(); - } - // Same thing with blend shapes - blendshapes = hfmMesh.blendshapes.toStdVector(); - for (auto& blendShape : blendshapes) { - if (!blendShape.normals.empty() && blendShape.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - blendShape.tangents.reserve(blendShape.normals.size()); - std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); - } - } - // evaluate all attribute elements and data sizes // Position is a vec3 @@ -73,12 +65,12 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) const auto normalElement = HFM_NORMAL_ELEMENT; - const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int normalsSize = (int)meshNormals.size() * normalElement.getSize(); const int tangentsSize = (int)meshTangents.size() * normalElement.getSize(); // If there are normals then there should be tangents assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { - HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in file"); + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in mesh"); } const auto normalsAndTangentsSize = normalsSize + tangentsSize; @@ -124,11 +116,11 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics if (normalsSize > 0) { std::vector normalsAndTangents; - normalsAndTangents.reserve(hfmMesh.normals.size() + (int)meshTangents.size()); - auto normalIt = hfmMesh.normals.constBegin(); + normalsAndTangents.reserve(meshNormals.size() + (int)meshTangents.size()); + auto normalIt = meshNormals.cbegin(); auto tangentIt = meshTangents.cbegin(); for (; - normalIt != hfmMesh.normals.constEnd(); + normalIt != meshNormals.cend(); ++normalIt, ++tangentIt) { #if HFM_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); @@ -212,11 +204,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics auto vertexFormat = std::make_shared(); auto vertexBufferStream = std::make_shared(); - // Decision time: - // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes - // else everything is interleaved in one buffer - - // Default case is no blend shapes gpu::BufferPointer attribBuffer; int totalAttribBufferSize = totalVertsSize; gpu::uint8 posChannel = 0; @@ -244,7 +231,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } } - // Pack normal and Tangent with the rest of atributes if no blend shapes + // Pack normal and Tangent with the rest of atributes if (colorsSize) { vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); bufOffset += colorElement.getSize(); @@ -384,22 +371,21 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - auto& meshes = input.get0(); - auto& url = input.get1(); - auto& meshIndicesToModelNames = input.get2(); + const auto& meshes = input.get0(); + const auto& url = input.get1(); + const auto& meshIndicesToModelNames = input.get2(); + const auto& normalsPerMesh = input.get3(); + const auto& tangentsPerMesh = input.get4(); + + auto& graphicsMeshes = output; - auto& graphicsMeshes = output.edit0(); - auto& tangentsPerMesh = output.edit1(); - auto& blendshapesPerMesh = output.edit2(); int n = (int)meshes.size(); for (int i = 0; i < n; i++) { graphicsMeshes.emplace_back(); auto& graphicsMesh = graphicsMeshes[i]; - tangentsPerMesh.emplace_back(); - blendshapesPerMesh.emplace_back(); // Try to create the graphics::Mesh - buildGraphicsMesh(meshes[i], graphicsMesh, tangentsPerMesh[i], blendshapesPerMesh[i]); + buildGraphicsMesh(meshes[i], graphicsMesh, normalsPerMesh[i], tangentsPerMesh[i]); // Choose a name for the mesh if (graphicsMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h index 3c8985ef9a..bb4136c086 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -20,8 +20,8 @@ class BuildGraphicsMeshTask { public: - using Input = baker::VaryingSet3, hifi::URL, baker::MeshIndicesToModelNames>; - using Output = baker::VaryingSet3, std::vector, std::vector>; + using Input = baker::VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Output = std::vector; using JobModel = baker::Job::ModelIO; void run(const baker::BakeContextPointer& context, const Input& input, Output& output); diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp new file mode 100644 index 0000000000..b908fa9ced --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp @@ -0,0 +1,70 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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 "CalculateBlendshapeNormalsTask.h" + +#include "ModelMath.h" + +void CalculateBlendshapeNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMesh = input.get0(); + const auto& meshes = input.get1(); + auto& normalsPerBlendshapePerMeshOut = output; + + normalsPerBlendshapePerMeshOut.reserve(blendshapesPerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& mesh = meshes[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + normalsPerBlendshapePerMeshOut.emplace_back(); + auto& normalsPerBlendshapeOut = normalsPerBlendshapePerMeshOut[normalsPerBlendshapePerMeshOut.size()-1]; + + normalsPerBlendshapeOut.reserve(blendshapes.size()); + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& normalsIn = blendshape.normals; + // Check if normals are already defined. Otherwise, calculate them from existing blendshape vertices. + if (!normalsIn.empty()) { + normalsPerBlendshapeOut.push_back(normalsIn.toStdVector()); + } else { + // Create lookup to get index in blendshape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + normalsPerBlendshapeOut.emplace_back(); + auto& normals = normalsPerBlendshapeOut[normalsPerBlendshapeOut.size()-1]; + normals.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&reverseIndices, &blendshape, &normals](int normalIndex) /* NormalAccessor */ { + const auto lookupIndex = reverseIndices[normalIndex]; + if (lookupIndex < blendshape.vertices.size()) { + return &normals[lookupIndex]; + } else { + // Index isn't in the blendshape. Request that the normal not be calculated. + return (glm::vec3*)nullptr; + } + }, + [&mesh, &reverseIndices, &blendshape](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + const auto lookupIndex = reverseIndices[vertexIndex]; + if (lookupIndex < blendshape.vertices.size()) { + outVertex = blendshape.vertices[lookupIndex]; + } else { + // Index isn't in the blendshape, so return vertex from mesh + outVertex = mesh.vertices[lookupIndex]; + } + }); + } + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h new file mode 100644 index 0000000000..0eb7d3c4e7 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateBlendshapeNormalsTask_h +#define hifi_CalculateBlendshapeNormalsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape normals if not already present in the blendshape +class CalculateBlendshapeNormalsTask { +public: + using Input = baker::VaryingSet2>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp new file mode 100644 index 0000000000..04e05f0378 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp @@ -0,0 +1,95 @@ +// +// CalculateBlendshapeTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/08. +// Copyright 2019 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 "CalculateBlendshapeTangentsTask.h" + +#include + +#include "ModelMath.h" + +void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerBlendshapePerMesh = input.get0(); + const auto& blendshapesPerMesh = input.get1(); + const auto& meshes = input.get2(); + const auto& materials = input.get3(); + auto& tangentsPerBlendshapePerMeshOut = output; + + tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + const auto& mesh = meshes[i]; + tangentsPerBlendshapePerMeshOut.emplace_back(); + auto& tangentsPerBlendshapeOut = tangentsPerBlendshapePerMeshOut[tangentsPerBlendshapePerMeshOut.size()-1]; + + // Check if we actually need to calculate the tangents, or just append empty arrays + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& tangentsIn = blendshape.tangents; + const auto& normals = normalsPerBlendshape[j]; + tangentsPerBlendshapeOut.emplace_back(); + auto& tangentsOut = tangentsPerBlendshapeOut[tangentsPerBlendshapeOut.size()-1]; + + // Check if we already have tangents + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we can and should calculate tangents (we need normals to calculate the tangents) + if (normals.empty() || !needTangents) { + continue; + } + tangentsOut.resize(normals.size()); + + // Create lookup to get index in blend shape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + baker::calculateTangents(mesh, + [&mesh, &blendshape, &normals, &tangentsOut, &reverseIndices](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + const auto index1 = reverseIndices[firstIndex]; + const auto index2 = reverseIndices[secondIndex]; + + if (index1 < blendshape.vertices.size()) { + outVertices[0] = blendshape.vertices[index1]; + outTexCoords[0] = mesh.texCoords[index1]; + outTexCoords[1] = mesh.texCoords[index2]; + if (index2 < blendshape.vertices.size()) { + outVertices[1] = blendshape.vertices[index2]; + } else { + // Index isn't in the blend shape so return vertex from mesh + outVertices[1] = mesh.vertices[secondIndex]; + } + outNormal = normals[index1]; + return &tangentsOut[index1]; + } else { + // Index isn't in blend shape so return nullptr + return (glm::vec3*)nullptr; + } + }); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h new file mode 100644 index 0000000000..c24b41d2d9 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateBlendshapeTangentsTask_h +#define hifi_CalculateBlendshapeTangentsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape tangents if not already present in the blendshape +class CalculateBlendshapeTangentsTask { +public: + using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, QHash>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp new file mode 100644 index 0000000000..a6884e104d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp @@ -0,0 +1,40 @@ +// +// CalculateMeshNormalsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// Copyright 2019 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 "CalculateMeshNormalsTask.h" + +#include "ModelMath.h" + +void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& meshes = input; + auto& normalsPerMeshOut = output; + + normalsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < (int)meshes.size(); i++) { + const auto& mesh = meshes[i]; + normalsPerMeshOut.emplace_back(); + auto& normalsOut = normalsPerMeshOut[normalsPerMeshOut.size()-1]; + // Only calculate normals if this mesh doesn't already have them + if (!mesh.normals.empty()) { + normalsOut = mesh.normals.toStdVector(); + } else { + normalsOut.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&normalsOut](int normalIndex) /* NormalAccessor */ { + return &normalsOut[normalIndex]; + }, + [&mesh](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + outVertex = mesh.vertices[vertexIndex]; + } + ); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h new file mode 100644 index 0000000000..b32780c68d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h @@ -0,0 +1,30 @@ +// +// CalculateMeshNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateMeshNormalsTask_h +#define hifi_CalculateMeshNormalsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh normals if not already present in the mesh +class CalculateMeshNormalsTask { +public: + using Input = std::vector; + using Output = baker::NormalsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp new file mode 100644 index 0000000000..e94e15507e --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -0,0 +1,65 @@ +// +// CalculateMeshTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// Copyright 2019 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 "CalculateMeshTangentsTask.h" + +#include "ModelMath.h" + +void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerMesh = input.get0(); + const std::vector& meshes = input.get1(); + const auto& materials = input.get2(); + auto& tangentsPerMeshOut = output; + + tangentsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < (int)meshes.size(); i++) { + const auto& mesh = meshes[i]; + const auto& tangentsIn = mesh.tangents; + const auto& normals = normalsPerMesh[i]; + tangentsPerMeshOut.emplace_back(); + auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; + + // Check if we already have tangents and therefore do not need to do any calculation + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we have normals, and if not then tangents can't be calculated + if (normals.empty()) { + continue; + } + + // Check if we actually need to calculate the tangents + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + if (needTangents) { + continue; + } + + tangentsOut.resize(normals.size()); + baker::calculateTangents(mesh, + [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = normals[firstIndex]; + outTexCoords[0] = mesh.texCoords[firstIndex]; + outTexCoords[1] = mesh.texCoords[secondIndex]; + return &(tangentsOut[firstIndex]); + }); + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h new file mode 100644 index 0000000000..b8fdb7d5f4 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h @@ -0,0 +1,32 @@ +// +// CalculateMeshTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateMeshTangentsTask_h +#define hifi_CalculateMeshTangentsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh tangents if not already present in the mesh +class CalculateMeshTangentsTask { +public: + using NormalsPerMesh = std::vector>; + + using Input = baker::VaryingSet3, QHash>; + using Output = baker::TangentsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/ModelMath.cpp b/libraries/model-baker/src/model-baker/ModelMath.cpp new file mode 100644 index 0000000000..50e7e89cf4 --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.cpp @@ -0,0 +1,121 @@ +// +// ModelMath.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/18. +// Copyright 2019 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 "ModelMath.h" + +#include +#include "ModelBakerLogging.h" + +namespace baker { + template + const T& checkedAt(const QVector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + const T& checkedAt(const std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + T& checkedAt(std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + void setTangent(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex) { + glm::vec3 vertex[2]; + glm::vec2 texCoords[2]; + glm::vec3 normal; + glm::vec3* tangent = vertexAccessor(firstIndex, secondIndex, vertex, texCoords, normal); + if (tangent) { + glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); + if (glm::length(bitangent) < EPSILON) { + return; + } + glm::vec2 texCoordDelta = texCoords[1] - texCoords[0]; + glm::vec3 normalizedNormal = glm::normalize(normal); + *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); + } + } + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexSetter) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + glm::vec3* n0 = normalAccessor(part.quadIndices[i]); + glm::vec3* n1 = normalAccessor(part.quadIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.quadIndices[i + 2]); + glm::vec3* n3 = normalAccessor(part.quadIndices[i + 3]); + if (!n0 || !n1 || !n2 || !n3) { + // Quad is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; // Assume all vertices in this quad are in the same plane, so only the first three are needed to calculate the normal + vertexSetter(part.quadIndices[i], vertices[0]); + vertexSetter(part.quadIndices[i + 1], vertices[1]); + vertexSetter(part.quadIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = *n3 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + glm::vec3* n0 = normalAccessor(part.triangleIndices[i]); + glm::vec3* n1 = normalAccessor(part.triangleIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.triangleIndices[i + 2]); + if (!n0 || !n1 || !n2) { + // Tri is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; + vertexSetter(part.triangleIndices[i], vertices[0]); + vertexSetter(part.triangleIndices[i + 1], vertices[1]); + vertexSetter(part.triangleIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateNormals: part.triangleIndices.size() is not divisible by three"); + } + } + } + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + setTangent(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1)); + setTangent(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); + setTangent(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); + setTangent(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i)); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + setTangent(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateTangents: part.triangleIndices.size() is not divisible by three"); + } + } + } +} + diff --git a/libraries/model-baker/src/model-baker/ModelMath.h b/libraries/model-baker/src/model-baker/ModelMath.h new file mode 100644 index 0000000000..2a909e6eed --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.h @@ -0,0 +1,34 @@ +// +// ModelMath.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "BakerTypes.h" + +namespace baker { + // Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed + using NormalAccessor = std::function; + + // Assigns a vertex to outVertex given the lookup index + using VertexSetter = std::function; + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexAccessor); + + // firstIndex, secondIndex: the vertex indices to be used for calculation + // outVertices: should be assigned a 2 element array containing the vertices at firstIndex and secondIndex + // outTexCoords: same as outVertices but for texture coordinates + // outNormal: reference to the normal of this triangle + // + // Return value: pointer to the tangent you want to be calculated + using IndexAccessor = std::function; + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor); +}; diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index e6e3b0e812..b6550a5e9e 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -111,146 +111,300 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater /**jsdoc * A material such as may be used by a {@link Entities.EntityType|Material} entity. * @typedef {object} Material - * @property {string} name="" - A name for the material. - * @property {string} model="hifi_pbr" - Currently not used. - * @property {Color|RGBS} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value - * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. - * @property {number} opacity=1.0 - The opacity, 0.01.0. - * @property {boolean} unlit=false - If true, the material is not lit. - * @property {Color|RGBS} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can - * be either RGB or sRGB. - * @property {number} roughness - The roughness, 0.01.0. - * @property {number} metallic - The metallicness, 0.01.0. - * @property {number} scattering - The scattering, 0.01.0. - * @property {string} emissiveMap - URL of emissive texture image. - * @property {string} albedoMap - URL of albedo texture image. + * @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes. + * Supported models are: "hifi_pbr" + * @property {string} name="" - A name for the material. Supported by all material models. + * @property {Color|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value + * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} opacity=1.0 - The opacity, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {boolean|string} unlit=false - If true, the material is not lit. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {Color|RGBS|string} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can + * be either RGB or sRGB. Set to "fallthrough" to fallthrough to the material below. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} roughness - The roughness, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} metallic - The metallicness, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} scattering - The scattering, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {string} emissiveMap - URL of emissive texture image. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {string} albedoMap - URL of albedo texture image. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. * @property {string} opacityMap - URL of opacity texture image. Set value the same as the albedoMap value for - * transparency. - * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. - * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. - * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. - * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. - * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. - * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. - * @property {string} occlusionMap - URL of occlusion texture image. + * transparency. "hifi_pbr" model only. + * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} occlusionMap - URL of occlusion texture image. Set to "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. * @property {string} scatteringMap - URL of scattering texture image. Only used if normalMap or - * bumpMap is specified. - * @property {string} lightMap - URL of light map texture image. Currently not used. + * bumpMap is specified. Set to "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} lightMap - URL of light map texture image. Currently not used.. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} texCoordTransform0 - The transform to use for all of the maps besides occlusionMap and lightMap. Currently unused. Set to + * "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. Currently unused. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} lightmapParams - Parameters for controlling how lightMap is used. Currently unused. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} materialParams - Parameters for controlling the material projection and repition. Currently unused. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {bool} defaultFallthrough=false - If true, all properties will fallthrough to the material below unless they are set. If + * false, they will respect the individual properties' fallthrough state. "hifi_pbr" model only. */ // Note: See MaterialEntityItem.h for default values used in practice. std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) { std::string name = ""; std::shared_ptr material = std::make_shared(); - for (auto& key : materialJSON.keys()) { - if (key == "name") { - auto nameJSON = materialJSON.value(key); - if (nameJSON.isString()) { - name = nameJSON.toString().toStdString(); - } - } else if (key == "model") { - auto modelJSON = materialJSON.value(key); - if (modelJSON.isString()) { - material->setModel(modelJSON.toString().toStdString()); - } - } else if (key == "emissive") { - glm::vec3 color; - bool isSRGB; - bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); - if (valid) { - material->setEmissive(color, isSRGB); - } - } else if (key == "opacity") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setOpacity(value.toDouble()); - } - } else if (key == "unlit") { - auto value = materialJSON.value(key); - if (value.isBool()) { - material->setUnlit(value.toBool()); - } - } else if (key == "albedo") { - glm::vec3 color; - bool isSRGB; - bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); - if (valid) { - material->setAlbedo(color, isSRGB); - } - } else if (key == "roughness") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setRoughness(value.toDouble()); - } - } else if (key == "metallic") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setMetallic(value.toDouble()); - } - } else if (key == "scattering") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setScattering(value.toDouble()); - } - } else if (key == "emissiveMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setEmissiveMap(baseUrl.resolved(value.toString())); - } - } else if (key == "albedoMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - QString urlString = value.toString(); - bool useAlphaChannel = false; - auto opacityMap = materialJSON.find("opacityMap"); - if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == urlString) { - useAlphaChannel = true; + + const std::string HIFI_PBR = "hifi_pbr"; + std::string modelString = HIFI_PBR; + auto modelJSONIter = materialJSON.find("model"); + if (modelJSONIter != materialJSON.end() && modelJSONIter.value().isString()) { + modelString = modelJSONIter.value().toString().toStdString(); + material->setModel(modelString); + } + + if (modelString == HIFI_PBR) { + const QString FALLTHROUGH("fallthrough"); + for (auto& key : materialJSON.keys()) { + if (key == "name") { + auto nameJSON = materialJSON.value(key); + if (nameJSON.isString()) { + name = nameJSON.toString().toStdString(); + } + } else if (key == "model") { + auto modelJSON = materialJSON.value(key); + if (modelJSON.isString()) { + material->setModel(modelJSON.toString().toStdString()); + } + } else if (key == "emissive") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::EMISSIVE_VAL_BIT); + } else { + glm::vec3 color; + bool isSRGB; + bool valid = parseJSONColor(value, color, isSRGB); + if (valid) { + material->setEmissive(color, isSRGB); + } + } + } else if (key == "opacity") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_VAL_BIT); + } else if (value.isDouble()) { + material->setOpacity(value.toDouble()); + } + } else if (key == "unlit") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::UNLIT_VAL_BIT); + } else if (value.isBool()) { + material->setUnlit(value.toBool()); + } + } else if (key == "albedo") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ALBEDO_VAL_BIT); + } else { + glm::vec3 color; + bool isSRGB; + bool valid = parseJSONColor(value, color, isSRGB); + if (valid) { + material->setAlbedo(color, isSRGB); + } + } + } else if (key == "roughness") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::GLOSSY_VAL_BIT); + } else if (value.isDouble()) { + material->setRoughness(value.toDouble()); + } + } else if (key == "metallic") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_VAL_BIT); + } else if (value.isDouble()) { + material->setMetallic(value.toDouble()); + } + } else if (key == "scattering") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::SCATTERING_VAL_BIT); + } else if (value.isDouble()) { + material->setScattering(value.toDouble()); + } + } else if (key == "emissiveMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::EMISSIVE_MAP_BIT); + } else { + material->setEmissiveMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "albedoMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + QString valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ALBEDO_MAP_BIT); + } else { + bool useAlphaChannel = false; + auto opacityMap = materialJSON.find("opacityMap"); + if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == valueString) { + useAlphaChannel = true; + } + material->setAlbedoMap(baseUrl.resolved(valueString), useAlphaChannel); + } + } + } else if (key == "roughnessMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ROUGHNESS_MAP_BIT); + } else { + material->setRoughnessMap(baseUrl.resolved(valueString), false); + } + } + } else if (key == "glossMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ROUGHNESS_MAP_BIT); + } else { + material->setRoughnessMap(baseUrl.resolved(valueString), true); + } + } + } else if (key == "metallicMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_MAP_BIT); + } else { + material->setMetallicMap(baseUrl.resolved(valueString), false); + } + } + } else if (key == "specularMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_MAP_BIT); + } else { + material->setMetallicMap(baseUrl.resolved(valueString), true); + } + } + } else if (key == "normalMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::NORMAL_MAP_BIT); + } else { + material->setNormalMap(baseUrl.resolved(valueString), false); + } + } + } else if (key == "bumpMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::NORMAL_MAP_BIT); + } else { + material->setNormalMap(baseUrl.resolved(valueString), true); + } + } + } else if (key == "occlusionMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OCCLUSION_MAP_BIT); + } else { + material->setOcclusionMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "scatteringMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::SCATTERING_MAP_BIT); + } else { + material->setScatteringMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "lightMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::LIGHTMAP_MAP_BIT); + } else { + material->setLightmapMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "texCoordTransform0") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM0); + } + } + // TODO: implement texCoordTransform0 + } else if (key == "texCoordTransform1") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM1); + } + } + // TODO: implement texCoordTransform1 + } else if (key == "lightmapParams") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::LIGHTMAP_PARAMS); + } + } + // TODO: implement lightmapParams + } else if (key == "materialParams") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::MATERIAL_PARAMS); + } + } + // TODO: implement materialParams + } else if (key == "defaultFallthrough") { + auto value = materialJSON.value(key); + if (value.isBool()) { + material->setDefaultFallthrough(value.toBool()); } - material->setAlbedoMap(baseUrl.resolved(urlString), useAlphaChannel); - } - } else if (key == "roughnessMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setRoughnessMap(baseUrl.resolved(value.toString()), false); - } - } else if (key == "glossMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setRoughnessMap(baseUrl.resolved(value.toString()), true); - } - } else if (key == "metallicMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setMetallicMap(baseUrl.resolved(value.toString()), false); - } - } else if (key == "specularMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setMetallicMap(baseUrl.resolved(value.toString()), true); - } - } else if (key == "normalMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setNormalMap(baseUrl.resolved(value.toString()), false); - } - } else if (key == "bumpMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setNormalMap(baseUrl.resolved(value.toString()), true); - } - } else if (key == "occlusionMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setOcclusionMap(baseUrl.resolved(value.toString())); - } - } else if (key == "scatteringMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setScatteringMap(baseUrl.resolved(value.toString())); - } - } else if (key == "lightMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setLightmapMap(baseUrl.resolved(value.toString())); } } } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 989661cb81..f74b337ee7 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -588,6 +588,29 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } +void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QString &oculusID) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest request; + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + QUrl grantURL = _authURL; + grantURL.setPath("/oauth/token"); + + QByteArray postData; + postData.append("grant_type=password&"); + postData.append("oculus_nonce=" + nonce + "&"); + postData.append("oculus_id=" + oculusID + "&"); + postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); + + request.setUrl(grantURL); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QNetworkReply* requestReply = networkAccessManager.post(request, postData); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); +} + void AccountManager::refreshAccessToken() { // we can't refresh our access token if we don't have a refresh token, so check for that first diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index ca2b826c98..477488031d 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -106,6 +106,7 @@ public: public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); + void requestAccessTokenWithOculus(const QString& nonce, const QString& oculusID); void requestAccessTokenWithAuthCode(const QString& authCode, const QString& clientId, const QString& clientSecret, diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp old mode 100644 new mode 100755 index fd57f2fa3a..a79f0a0c2b --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -77,8 +77,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) { _bytesAvailable -= length; success = true; _dirty = true; - } - + } + #ifdef WANT_DEBUG if (!success) { qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING...."; @@ -97,7 +97,7 @@ bool OctreePacketData::append(unsigned char byte) { if (_bytesAvailable > 0) { _uncompressed[_bytesInUse] = byte; _bytesInUse++; - _bytesAvailable--; + _bytesAvailable--; success = true; _dirty = true; } @@ -110,13 +110,13 @@ bool OctreePacketData::reserveBitMask() { bool OctreePacketData::reserveBytes(int numberOfBytes) { bool success = false; - + if (_bytesAvailable >= numberOfBytes) { _bytesReserved += numberOfBytes; _bytesAvailable -= numberOfBytes; success = true; } - + return success; } @@ -188,7 +188,7 @@ bool OctreePacketData::startSubTree(const unsigned char* octcode) { const unsigned char* OctreePacketData::getFinalizedData() { if (!_enableCompression) { - return &_uncompressed[0]; + return &_uncompressed[0]; } if (_dirty) { @@ -197,7 +197,7 @@ const unsigned char* OctreePacketData::getFinalizedData() { } compressContent(); } - return &_compressed[0]; + return &_compressed[0]; } int OctreePacketData::getFinalizedSize() { @@ -223,7 +223,7 @@ void OctreePacketData::endSubTree() { void OctreePacketData::discardSubTree() { int bytesInSubTree = _bytesInUse - _subTreeAt; _bytesInUse -= bytesInSubTree; - _bytesAvailable += bytesInSubTree; + _bytesAvailable += bytesInSubTree; _subTreeAt = _bytesInUse; // should be the same actually... _dirty = true; @@ -231,7 +231,7 @@ void OctreePacketData::discardSubTree() { int reduceBytesOfOctalCodes = _bytesOfOctalCodes - _bytesOfOctalCodesCurrentSubTree; _bytesOfOctalCodes = _bytesOfOctalCodesCurrentSubTree; _totalBytesOfOctalCodes -= reduceBytesOfOctalCodes; - + // if we discard the subtree then reset reserved bytes to the value when we started the subtree _bytesReserved = _subTreeBytesReserved; } @@ -243,7 +243,7 @@ LevelDetails OctreePacketData::startLevel() { void OctreePacketData::discardLevel(LevelDetails key) { int bytesInLevel = _bytesInUse - key._startIndex; - + // reset statistics... int reduceBytesOfOctalCodes = _bytesOfOctalCodes - key._bytesOfOctalCodes; int reduceBytesOfBitMasks = _bytesOfBitMasks - key._bytesOfBitmasks; @@ -261,11 +261,11 @@ void OctreePacketData::discardLevel(LevelDetails key) { qCDebug(octree, "discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d", debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); } - + _bytesInUse -= bytesInLevel; - _bytesAvailable += bytesInLevel; + _bytesAvailable += bytesInLevel; _dirty = true; - + // reserved bytes are reset to the value when the level started _bytesReserved = key._bytesReservedAtStart; @@ -333,7 +333,7 @@ bool OctreePacketData::appendValue(uint8_t value) { bool OctreePacketData::appendValue(uint16_t value) { const unsigned char* data = (const unsigned char*)&value; - + int length = sizeof(value); bool success = append(data, length); if (success) { @@ -506,10 +506,11 @@ bool OctreePacketData::appendValue(bool value) { bool OctreePacketData::appendValue(const QString& string) { // TODO: make this a ByteCountCoded leading byte - uint16_t length = string.size() + 1; // include NULL + QByteArray utf8Array = string.toUtf8(); + uint16_t length = utf8Array.length(); // no NULL bool success = appendValue(length); if (success) { - success = appendRawData((const unsigned char*)qPrintable(string), length); + success = appendRawData((const unsigned char*)utf8Array.constData(), length); } return success; } @@ -666,7 +667,7 @@ void OctreePacketData::debugContent() { } } printf("\n"); - + qCDebug(octree, "OctreePacketData::debugContent()... UNCOMPRESSED DATA.... size=%d",_bytesInUse); perline=0; for (int i = 0; i < _bytesInUse; i++) { @@ -702,16 +703,16 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, glm::u return sizeof(result); } -int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); dataBytes += sizeof(length); - QString value((const char*)dataBytes); + QString value = QString::fromUtf8((const char*)dataBytes, length); result = value; return sizeof(length) + length; } -int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result) { +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); dataBytes += sizeof(length); @@ -819,4 +820,4 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, AACube int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QRect& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); -} \ No newline at end of file +} diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 8fd6d4eada..d5ded6f909 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -109,7 +109,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { } _dynamicsWorld = nullptr; } - int32_t collisionGroup = computeCollisionGroup(); + int32_t collisionMask = computeCollisionMask(); + int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR; if (_rigidBody) { updateMassProperties(); } @@ -117,7 +118,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { // add to new world _dynamicsWorld = world; _pendingFlags &= ~PENDING_FLAG_JUMP; - _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); + _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, collisionMask); _dynamicsWorld->addAction(this); // restore gravity settings because adding an object to the world overwrites its gravity setting _rigidBody->setGravity(_currentGravity * _currentUp); @@ -127,7 +128,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); } - _ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); + _ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); if (_rigidBody) { @@ -384,8 +385,8 @@ static const char* stateToStr(CharacterController::State state) { #endif // #ifdef DEBUG_STATE_CHANGE void CharacterController::updateCurrentGravity() { - int32_t collisionGroup = computeCollisionGroup(); - if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { + int32_t collisionMask = computeCollisionMask(); + if (_state == State::Hover || collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { _currentGravity = 0.0f; } else { _currentGravity = _gravity; @@ -458,28 +459,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const void CharacterController::setCollisionless(bool collisionless) { if (collisionless != _collisionless) { _collisionless = collisionless; - _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; - } -} - -int32_t CharacterController::computeCollisionGroup() const { - if (_collisionless) { - return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR; - } else { - return BULLET_COLLISION_GROUP_MY_AVATAR; - } -} - -void CharacterController::handleChangedCollisionGroup() { - if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) { - // ATM the easiest way to update collision groups is to remove/re-add the RigidBody - if (_dynamicsWorld) { - _dynamicsWorld->removeRigidBody(_rigidBody); - int32_t collisionGroup = computeCollisionGroup(); - _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); - } - _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; - updateCurrentGravity(); + _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } } @@ -567,8 +547,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel btScalar angle = motor.rotation.getAngle(); btVector3 velocity = worldVelocity.rotate(axis, -angle); - int32_t collisionGroup = computeCollisionGroup(); - if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS || + int32_t collisionMask = computeCollisionMask(); + if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS || _state == State::Hover || motor.hTimescale == motor.vTimescale) { // modify velocity btScalar tau = dt / motor.hTimescale; @@ -708,11 +688,11 @@ void CharacterController::updateState() { btVector3 rayStart = _position; btScalar rayLength = _radius; - int32_t collisionGroup = computeCollisionGroup(); - if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { - rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; - } else { + int32_t collisionMask = computeCollisionMask(); + if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { rayLength += MIN_HOVER_HEIGHT; + } else { + rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; } btVector3 rayEnd = rayStart - rayLength * _currentUp; @@ -746,69 +726,7 @@ void CharacterController::updateState() { // disable normal state transitions while collisionless const btScalar MAX_WALKING_SPEED = 2.65f; - if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { - switch (_state) { - case State::Ground: - if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground detected"); - } else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) { - _takeoffJumpButtonID = _jumpButtonDownCount; - _takeoffToInAirStartTime = now; - SET_STATE(State::Takeoff, "jump pressed"); - } else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) { - SET_STATE(State::InAir, "falling"); - } - break; - case State::Takeoff: - if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground"); - } else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) { - SET_STATE(State::InAir, "takeoff done"); - - // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. - const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); - const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); - velocity += jumpSpeed * _currentUp; - _rigidBody->setLinearVelocity(velocity); - } - break; - case State::InAir: { - const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); - const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); - if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { - SET_STATE(State::Ground, "hit ground"); - } else if (_flyingAllowed) { - btVector3 desiredVelocity = _targetVelocity; - if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { - desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); - } - bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED; - if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { - SET_STATE(State::Hover, "double jump button"); - } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { - SET_STATE(State::Hover, "jump button held"); - } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { - // Transition to hover if we are above the fall threshold - SET_STATE(State::Hover, "above fall threshold"); - } - } else if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground detected"); - } - break; - } - case State::Hover: - btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); - bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); - if (!_flyingAllowed && rayHasHit) { - SET_STATE(State::InAir, "flying not allowed"); - } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { - SET_STATE(State::InAir, "near ground"); - } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { - SET_STATE(State::Ground, "touching ground"); - } - break; - } - } else { + if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { // when collisionless: only switch between State::Ground and State::Hover // and bypass state debugging if (rayHasHit) { @@ -820,6 +738,68 @@ void CharacterController::updateState() { } else { _state = State::Hover; } + } else { + switch (_state) { + case State::Ground: + if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground detected"); + } else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) { + _takeoffJumpButtonID = _jumpButtonDownCount; + _takeoffToInAirStartTime = now; + SET_STATE(State::Takeoff, "jump pressed"); + } else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) { + SET_STATE(State::InAir, "falling"); + } + break; + case State::Takeoff: + if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground"); + } else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) { + SET_STATE(State::InAir, "takeoff done"); + + // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + velocity += jumpSpeed * _currentUp; + _rigidBody->setLinearVelocity(velocity); + } + break; + case State::InAir: { + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { + SET_STATE(State::Ground, "hit ground"); + } else if (_flyingAllowed) { + btVector3 desiredVelocity = _targetVelocity; + if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { + desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); + } + bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED; + if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { + SET_STATE(State::Hover, "double jump button"); + } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { + SET_STATE(State::Hover, "jump button held"); + } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { + // Transition to hover if we are above the fall threshold + SET_STATE(State::Hover, "above fall threshold"); + } + } else if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground detected"); + } + break; + } + case State::Hover: + btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); + bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); + if (!_flyingAllowed && rayHasHit) { + SET_STATE(State::InAir, "flying not allowed"); + } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { + SET_STATE(State::InAir, "near ground"); + } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { + SET_STATE(State::Ground, "touching ground"); + } + break; + } } } @@ -866,6 +846,6 @@ void CharacterController::setFlyingAllowed(bool value) { void CharacterController::setCollisionlessAllowed(bool value) { if (value != _collisionlessAllowed) { _collisionlessAllowed = value; - _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; + _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h old mode 100644 new mode 100755 index 50db2bea12..cac37da0b9 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -30,7 +30,7 @@ const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0; const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2; const uint32_t PENDING_FLAG_JUMP = 1U << 3; -const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; +const uint32_t PENDING_FLAG_UPDATE_COLLISION_MASK = 1U << 4; const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5; const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); @@ -120,14 +120,16 @@ public: bool isStuck() const { return _isStuck; } void setCollisionless(bool collisionless); - int32_t computeCollisionGroup() const; - void handleChangedCollisionGroup(); + + virtual int32_t computeCollisionMask() const = 0; + virtual void handleChangedCollisionMask() = 0; bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); void setFlyingAllowed(bool value); void setCollisionlessAllowed(bool value); + void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } protected: #ifdef DEBUG_STATE_CHANGE diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 9814e358c3..a82931064a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -81,7 +81,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer setShape(shape); if (_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { - // avatar entities entities are always thus, so we cache this fact in _ownershipState + // avatar entities are always thus, so we cache this fact in _ownershipState _ownershipState = EntityMotionState::OwnershipState::Unownable; } @@ -211,6 +211,7 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } if (_entity->isMovingRelativeToParent() || _entity->hasActions() || + _entity->hasGrabs() || _entity->hasAncestorOfType(NestableType::Avatar)) { return MOTION_TYPE_KINEMATIC; } @@ -235,11 +236,19 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { assert(entityTreeIsLocked()); if (_motionType == MOTION_TYPE_KINEMATIC) { BT_PROFILE("kinematicIntegration"); + uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); + if (hasInternalKinematicChanges()) { + // ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: This kinematic body was moved by an Action + // and doesn't require transform update because the body is authoritative and its transform + // has already been copied out --> do no kinematic integration. + clearInternalKinematicChanges(); + _lastKinematicStep = thisStep; + return; + } // This is physical kinematic motion which steps strictly by the subframe count // of the physics simulation and uses full gravity for acceleration. _entity->setAcceleration(_entity->getGravity()); - uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _lastKinematicStep = thisStep; _entity->stepKinematicMotion(dt); @@ -767,6 +776,7 @@ bool EntityMotionState::shouldSendBid() const { // NOTE: this method is only ever called when the entity's simulation is NOT locally owned return _body->isActive() && (_region == workload::Region::R1) + && _ownershipState != EntityMotionState::OwnershipState::Unownable && glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority() && !_entity->getLocked(); } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 653e3f4252..ddf384dc77 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -88,7 +88,6 @@ public: virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; bool shouldSendBid() const; - uint8_t computeFinalBidPriority() const; bool isLocallyOwned() const override; bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() @@ -100,6 +99,7 @@ public: void saveKinematicState(btScalar timeStep) override; protected: + uint8_t computeFinalBidPriority() const; void updateSendVelocities(); uint64_t getNextBidExpiry() const { return _nextBidExpiry; } void initForBid(); diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 74173c3f47..e5a61834e4 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -160,8 +160,9 @@ public: bool hasInternalKinematicChanges() const { return _hasInternalKinematicChanges; } - void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; } - void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } + // these methods are declared const so they can be called inside other const methods + void dirtyInternalKinematicChanges() const { _hasInternalKinematicChanges = true; } + void clearInternalKinematicChanges() const { _hasInternalKinematicChanges = false; } virtual bool isLocallyOwned() const { return false; } virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() @@ -185,8 +186,11 @@ protected: btRigidBody* _body { nullptr }; float _density { 1.0f }; + // ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: These date members allow an Action + // to operate on a kinematic object without screwing up our default kinematic integration + // which is done in the MotionState::getWorldTransform(). mutable uint32_t _lastKinematicStep; - bool _hasInternalKinematicChanges { false }; + mutable bool _hasInternalKinematicChanges { false }; }; using SetOfMotionStates = QSet; diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 17a52f7cd9..4094097741 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -100,9 +100,11 @@ void ThreadSafeDynamicsWorld::synchronizeMotionState(btRigidBody* body) { if (body->isKinematicObject()) { ObjectMotionState* objectMotionState = static_cast(body->getMotionState()); if (objectMotionState->hasInternalKinematicChanges()) { - // this is a special case where the kinematic motion has been updated by an Action - // so we supply the body's current transform to the MotionState - objectMotionState->clearInternalKinematicChanges(); + // ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: + // This is a special case where the kinematic motion has been updated by an Action + // so we supply the body's current transform to the MotionState, + // but we DON'T clear the internalKinematicChanges bit here because + // objectMotionState.getWorldTransform() will use and clear it later body->getMotionState()->setWorldTransform(body->getWorldTransform()); } return; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index ad49ceafe6..aa52e57c3f 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -142,6 +142,7 @@ public: // Rendering support virtual void setContext(const gpu::ContextPointer& context) final { _gpuContext = context; } virtual void submitFrame(const gpu::FramePointer& newFrame) = 0; + virtual void captureFrame(const std::string& outputName) const { } virtual float getRenderResolutionScale() const { return _renderResolutionScale; @@ -217,6 +218,9 @@ public: static const QString& MENU_PATH(); + // for updating plugin-related commands. Mimics the input plugin. + virtual void pluginUpdate() = 0; + signals: void recommendedFramebufferSizeChanged(const QSize& size); void resetSensorsRequested(); diff --git a/libraries/plugins/src/plugins/Forward.h b/libraries/plugins/src/plugins/Forward.h index 90746d648e..fc1e12b639 100644 --- a/libraries/plugins/src/plugins/Forward.h +++ b/libraries/plugins/src/plugins/Forward.h @@ -21,6 +21,7 @@ class DisplayPlugin; class InputPlugin; class CodecPlugin; class SteamClientPlugin; +class OculusPlatformPlugin; class Plugin; class PluginContainer; class PluginManager; @@ -35,4 +36,5 @@ using CodecPluginPointer = std::shared_ptr; using CodecPluginList = std::vector; using CodecPluginProvider = std::function; using SteamClientPluginPointer = std::shared_ptr; +using OculusPlatformPluginPointer = std::shared_ptr; using InputPluginSettingsPersister = std::function; diff --git a/libraries/plugins/src/plugins/OculusPlatformPlugin.h b/libraries/plugins/src/plugins/OculusPlatformPlugin.h new file mode 100644 index 0000000000..93bf534c6e --- /dev/null +++ b/libraries/plugins/src/plugins/OculusPlatformPlugin.h @@ -0,0 +1,28 @@ +// +// Created by Wayne Chen on 2018/12/20 +// 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 +// +#pragma once + +#include + +#include + +using NonceUserIDCallback = std::function; + +class OculusPlatformPlugin { +public: + virtual ~OculusPlatformPlugin() = default; + + virtual QString getName() const = 0; + virtual QString getOculusUserID() const = 0; + + virtual bool isRunning() const = 0; + + virtual void requestNonceAndUserID(NonceUserIDCallback callback) = 0; + + virtual void handleOVREvents() = 0; +}; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index a75ede3f03..0d0209e35f 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -14,6 +14,11 @@ #include #include +//#define HIFI_PLUGINMANAGER_DEBUG +#if defined(HIFI_PLUGINMANAGER_DEBUG) +#include +#endif + #include #include @@ -79,10 +84,7 @@ bool isDisabled(QJsonObject metaData) { return false; } -using Loader = QSharedPointer; -using LoaderList = QList; - -const LoaderList& getLoadedPlugins() { + auto PluginManager::getLoadedPlugins() const -> const LoaderList& { static std::once_flag once; static LoaderList loadedPlugins; std::call_once(once, [&] { @@ -106,15 +108,25 @@ const LoaderList& getLoadedPlugins() { for (auto plugin : candidates) { qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin); QSharedPointer loader(new QPluginLoader(pluginPath + plugin)); - - if (isDisabled(loader->metaData())) { + const QJsonObject pluginMetaData = loader->metaData(); +#if defined(HIFI_PLUGINMANAGER_DEBUG) + QJsonDocument metaDataDoc(pluginMetaData); + qCInfo(plugins) << "Metadata for " << qPrintable(plugin) << ": " << QString(metaDataDoc.toJson()); +#endif + if (isDisabled(pluginMetaData)) { qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "is disabled"; // Skip this one, it's disabled continue; } - if (getPluginInterfaceVersionFromMetaData(loader->metaData()) != HIFI_PLUGIN_INTERFACE_VERSION) { + + if (!_pluginFilter(pluginMetaData)) { + qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "doesn't pass provided filter"; + continue; + } + + if (getPluginInterfaceVersionFromMetaData(pluginMetaData) != HIFI_PLUGIN_INTERFACE_VERSION) { qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "interface version doesn't match, not loading:" - << getPluginInterfaceVersionFromMetaData(loader->metaData()) + << getPluginInterfaceVersionFromMetaData(pluginMetaData) << "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION; continue; } @@ -176,6 +188,22 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() { return steamClientPlugin; } +const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() { + static OculusPlatformPluginPointer oculusPlatformPlugin; + static std::once_flag once; + std::call_once(once, [&] { + // Now grab the dynamic plugins + for (auto loader : getLoadedPlugins()) { + OculusPlatformProvider* oculusPlatformProvider = qobject_cast(loader->instance()); + if (oculusPlatformProvider) { + oculusPlatformPlugin = oculusPlatformProvider->getOculusPlatformPlugin(); + break; + } + } + }); + return oculusPlatformPlugin; +} + const DisplayPluginList& PluginManager::getDisplayPlugins() { static std::once_flag once; static auto deviceAddedCallback = [](QString deviceName) { diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 2a002577a4..1a578c7406 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -13,8 +13,7 @@ #include "Forward.h" - -class PluginManager; +class QPluginLoader; using PluginManagerPointer = QSharedPointer; class PluginManager : public QObject, public Dependency { @@ -28,6 +27,7 @@ public: const InputPluginList& getInputPlugins(); const CodecPluginList& getCodecPlugins(); const SteamClientPluginPointer getSteamClientPlugin(); + const OculusPlatformPluginPointer getOculusPlatformPlugin(); DisplayPluginList getPreferredDisplayPlugins(); void setPreferredDisplayPlugins(const QStringList& displays); @@ -47,6 +47,9 @@ public: void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); QStringList getRunningInputDeviceNames() const; + using PluginFilter = std::function; + void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; } + signals: void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices); @@ -60,6 +63,12 @@ private: PluginContainer* _container { nullptr }; DisplayPluginList _displayPlugins; InputPluginList _inputPlugins; + PluginFilter _pluginFilter { [](const QJsonObject&) { return true; } }; + + using Loader = QSharedPointer; + using LoaderList = QList; + + const LoaderList& getLoadedPlugins() const; }; // TODO: we should define this value in CMake, and then use CMake diff --git a/libraries/plugins/src/plugins/RuntimePlugin.h b/libraries/plugins/src/plugins/RuntimePlugin.h index 9a7d6e0638..756b4ff585 100644 --- a/libraries/plugins/src/plugins/RuntimePlugin.h +++ b/libraries/plugins/src/plugins/RuntimePlugin.h @@ -51,5 +51,13 @@ public: virtual SteamClientPluginPointer getSteamClientPlugin() = 0; }; +class OculusPlatformProvider { +public: + virtual OculusPlatformPluginPointer getOculusPlatformPlugin() = 0; +}; + #define SteamClientProvider_iid "com.highfidelity.plugins.steamclient" Q_DECLARE_INTERFACE(SteamClientProvider, SteamClientProvider_iid) + +#define OculusPlatformProvider_iid "com.highfidelity.plugins.oculusplatform" +Q_DECLARE_INTERFACE(OculusPlatformProvider, OculusPlatformProvider_iid) \ No newline at end of file diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 7a52ad77da..bb2e784807 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -49,8 +49,6 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr } } -const graphics::MaterialPointer MeshPartPayload::DEFAULT_MATERIAL = std::make_shared(); - MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material) { updateMeshPart(mesh, partIndex); addMaterial(graphics::MaterialLayer(material, 0)); @@ -85,11 +83,13 @@ void MeshPartPayload::updateKey(const render::ItemKey& key) { ItemKey::Builder builder(key); builder.withTypeShape(); - if (topMaterialExists()) { - auto matKey = _drawMaterials.top().material->getKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } + if (_drawMaterials.needsUpdate()) { + RenderPipelines::updateMultiMaterial(_drawMaterials); + } + + auto matKey = _drawMaterials.getMaterialKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); } _itemKey = builder.build(); @@ -104,10 +104,7 @@ Item::Bound MeshPartPayload::getBound() const { } ShapeKey MeshPartPayload::getShapeKey() const { - graphics::MaterialKey drawMaterialKey; - if (topMaterialExists()) { - drawMaterialKey = _drawMaterials.top().material->getKey(); - } + graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey(); ShapeKey::Builder builder; builder.withMaterial(); @@ -158,7 +155,7 @@ void MeshPartPayload::render(RenderArgs* args) { // apply material properties if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { - RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); + RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing); args->_details._materialSwitches++; } @@ -332,11 +329,13 @@ void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { builder.withDeformed(); } - if (topMaterialExists()) { - auto matKey = _drawMaterials.top().material->getKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } + if (_drawMaterials.needsUpdate()) { + RenderPipelines::updateMultiMaterial(_drawMaterials); + } + + auto matKey = _drawMaterials.getMaterialKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); } _itemKey = builder.build(); @@ -348,11 +347,12 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, PrimitiveMode pr return; } - graphics::MaterialKey drawMaterialKey; - if (topMaterialExists()) { - drawMaterialKey = _drawMaterials.top().material->getKey(); + if (_drawMaterials.needsUpdate()) { + RenderPipelines::updateMultiMaterial(_drawMaterials); } + graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey(); + bool isTranslucent = drawMaterialKey.isTranslucent(); bool hasTangents = drawMaterialKey.isNormalMap() && _hasTangents; bool hasLightmap = drawMaterialKey.isLightmapMap(); @@ -435,7 +435,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { // apply material properties if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { - RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); + RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing); args->_details._materialSwitches++; } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 3b0590b4a9..deae91dda9 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -66,18 +66,15 @@ public: graphics::Mesh::Part _drawPart; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return topMaterialExists() ? _drawMaterials.top().material->getTextureSize() : 0; } - int getMaterialTextureCount() { return topMaterialExists() ? _drawMaterials.top().material->getTextureCount() : 0; } - bool hasTextureInfo() const { return topMaterialExists() ? _drawMaterials.top().material->hasTextureInfo() : false; } + size_t getMaterialTextureSize() { return _drawMaterials.getTextureSize(); } + int getMaterialTextureCount() { return _drawMaterials.getTextureCount(); } + bool hasTextureInfo() const { return _drawMaterials.hasTextureInfo(); } void addMaterial(graphics::MaterialLayer material); void removeMaterial(graphics::MaterialPointer material); protected: - static const graphics::MaterialPointer DEFAULT_MATERIAL; render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() }; - - bool topMaterialExists() const { return !_drawMaterials.empty() && _drawMaterials.top().material; } }; namespace render { diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index cd685a54a1..07dc683719 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -308,24 +308,33 @@ void addPlumberPipeline(ShapePlumber& plumber, void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args) { // Set a default albedo map - batch.setResourceTexture(gr::Texture::MaterialAlbedo, - DependencyManager::get()->getWhiteTexture()); + batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); // Set a default material if (pipeline.locations->materialBufferUnit) { // Create a default schema - static bool isMaterialSet = false; - static graphics::Material material; - if (!isMaterialSet) { - material.setAlbedo(vec3(1.0f)); - material.setOpacity(1.0f); - material.setMetallic(0.1f); - material.setRoughness(0.9f); - isMaterialSet = true; - } + static gpu::BufferView schemaBuffer; + static std::once_flag once; + std::call_once(once, [] { + graphics::MultiMaterial::Schema schema; + graphics::MaterialKey schemaKey; - // Set a default schema - batch.setUniformBuffer(gr::Buffer::Material, material.getSchemaBuffer()); + schema._albedo = vec3(1.0f); + schema._opacity = 1.0f; + schema._metallic = 0.1f; + schema._roughness = 0.9f; + + schemaKey.setAlbedo(true); + schemaKey.setTranslucentFactor(false); + schemaKey.setMetallic(true); + schemaKey.setGlossy(true); + schema._key = (uint32_t)schemaKey._flags.to_ulong(); + + auto schemaSize = sizeof(graphics::MultiMaterial::Schema); + schemaBuffer = gpu::BufferView(std::make_shared(schemaSize, (const gpu::Byte*) &schema, schemaSize)); + }); + + batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer); } } @@ -364,103 +373,400 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, con gpu::Shader::createProgram(deformed_model_shadow_fade_dq), state, extraBatchSetter, itemSetter); } -// FIXME find a better way to setup the default textures -void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) { - if (!material) { +void RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) { + graphics::MultiMaterial multiMaterial; + multiMaterial.push(graphics::MaterialLayer(material, 0)); + bindMaterials(multiMaterial, batch, enableTextures); +} + +void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) { + auto& schemaBuffer = multiMaterial.getSchemaBuffer(); + + if (multiMaterial.size() == 0) { + schemaBuffer.edit() = graphics::MultiMaterial::Schema(); return; } + auto& drawMaterialTextures = multiMaterial.getTextureTable(); + multiMaterial.setTexturesLoading(false); + + // The total list of things we need to look for + static std::set allFlags; + static std::once_flag once; + std::call_once(once, [] { + for (int i = 0; i < graphics::Material::NUM_TOTAL_FLAGS; i++) { + // The opacity mask/map are derived from the albedo map + if (i != graphics::MaterialKey::OPACITY_MASK_MAP_BIT && + i != graphics::MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT) { + allFlags.insert(i); + } + } + }); + + graphics::MultiMaterial materials = multiMaterial; + graphics::MultiMaterial::Schema schema; + graphics::MaterialKey schemaKey; + + std::set flagsToCheck = allFlags; + std::set flagsToSetDefault; + + while (!materials.empty()) { + auto material = materials.top().material; + if (!material) { + break; + } + materials.pop(); + + bool defaultFallthrough = material->getDefaultFallthrough(); + const auto& materialKey = material->getKey(); + const auto& textureMaps = material->getTextureMaps(); + + auto it = flagsToCheck.begin(); + while (it != flagsToCheck.end()) { + auto flag = *it; + bool fallthrough = defaultFallthrough || material->getPropertyFallthrough(flag); + + bool wasSet = false; + bool forceDefault = false; + switch (flag) { + case graphics::MaterialKey::EMISSIVE_VAL_BIT: + if (materialKey.isEmissive()) { + schema._emissive = material->getEmissive(false); + schemaKey.setEmissive(true); + wasSet = true; + } + break; + case graphics::MaterialKey::UNLIT_VAL_BIT: + if (materialKey.isUnlit()) { + schemaKey.setUnlit(true); + wasSet = true; + } + break; + case graphics::MaterialKey::ALBEDO_VAL_BIT: + if (materialKey.isAlbedo()) { + schema._albedo = material->getAlbedo(false); + schemaKey.setAlbedo(true); + wasSet = true; + } + break; + case graphics::MaterialKey::METALLIC_VAL_BIT: + if (materialKey.isMetallic()) { + schema._metallic = material->getMetallic(); + schemaKey.setMetallic(true); + wasSet = true; + } + break; + case graphics::MaterialKey::GLOSSY_VAL_BIT: + if (materialKey.isRough() || materialKey.isGlossy()) { + schema._roughness = material->getRoughness(); + schemaKey.setGlossy(materialKey.isGlossy()); + wasSet = true; + } + break; + case graphics::MaterialKey::OPACITY_VAL_BIT: + if (materialKey.isTranslucentFactor()) { + schema._opacity = material->getOpacity(); + schemaKey.setTranslucentFactor(true); + wasSet = true; + } + break; + case graphics::MaterialKey::SCATTERING_VAL_BIT: + if (materialKey.isScattering()) { + schema._scattering = material->getScattering(); + schemaKey.setScattering(true); + wasSet = true; + } + break; + case graphics::MaterialKey::ALBEDO_MAP_BIT: + if (materialKey.isAlbedoMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setAlbedoMap(true); + schemaKey.setOpacityMaskMap(materialKey.isOpacityMaskMap()); + schemaKey.setTranslucentMap(materialKey.isTranslucentMap()); + } + break; + case graphics::MaterialKey::METALLIC_MAP_BIT: + if (materialKey.isMetallicMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setMetallicMap(true); + } + break; + case graphics::MaterialKey::ROUGHNESS_MAP_BIT: + if (materialKey.isRoughnessMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setRoughnessMap(true); + } + break; + case graphics::MaterialKey::NORMAL_MAP_BIT: + if (materialKey.isNormalMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setNormalMap(true); + } + break; + case graphics::MaterialKey::OCCLUSION_MAP_BIT: + if (materialKey.isOcclusionMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setOcclusionMap(true); + } + break; + case graphics::MaterialKey::SCATTERING_MAP_BIT: + if (materialKey.isScatteringMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setScattering(true); + } + break; + case graphics::MaterialKey::EMISSIVE_MAP_BIT: + // Lightmap takes precendence over emissive map for legacy reasons + if (materialKey.isEmissiveMap() && !materialKey.isLightmapMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setEmissiveMap(true); + } else if (materialKey.isLightmapMap()) { + // We'll set this later when we check the lightmap + wasSet = true; + } + break; + case graphics::MaterialKey::LIGHTMAP_MAP_BIT: + if (materialKey.isLightmapMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setLightmapMap(true); + } + break; + case graphics::Material::TEXCOORDTRANSFORM0: + if (!fallthrough) { + schema._texcoordTransforms[0] = material->getTexCoordTransform(0); + wasSet = true; + } + break; + case graphics::Material::TEXCOORDTRANSFORM1: + if (!fallthrough) { + schema._texcoordTransforms[1] = material->getTexCoordTransform(1); + wasSet = true; + } + break; + case graphics::Material::LIGHTMAP_PARAMS: + if (!fallthrough) { + schema._lightmapParams = material->getLightmapParams(); + wasSet = true; + } + break; + case graphics::Material::MATERIAL_PARAMS: + if (!fallthrough) { + schema._materialParams = material->getMaterialParams(); + wasSet = true; + } + break; + default: + break; + } + + if (wasSet) { + flagsToCheck.erase(it++); + } else if (forceDefault || !fallthrough) { + flagsToSetDefault.insert(flag); + flagsToCheck.erase(it++); + } else { + ++it; + } + } + + if (flagsToCheck.empty()) { + break; + } + } + + for (auto flagBit : flagsToCheck) { + flagsToSetDefault.insert(flagBit); + } + + auto textureCache = DependencyManager::get(); + // Handle defaults + for (auto flag : flagsToSetDefault) { + switch (flag) { + case graphics::MaterialKey::EMISSIVE_VAL_BIT: + case graphics::MaterialKey::UNLIT_VAL_BIT: + case graphics::MaterialKey::ALBEDO_VAL_BIT: + case graphics::MaterialKey::METALLIC_VAL_BIT: + case graphics::MaterialKey::GLOSSY_VAL_BIT: + case graphics::MaterialKey::OPACITY_VAL_BIT: + case graphics::MaterialKey::SCATTERING_VAL_BIT: + case graphics::Material::TEXCOORDTRANSFORM0: + case graphics::Material::TEXCOORDTRANSFORM1: + case graphics::Material::LIGHTMAP_PARAMS: + case graphics::Material::MATERIAL_PARAMS: + // these are initialized to the correct default values in Schema() + break; + case graphics::MaterialKey::ALBEDO_MAP_BIT: + if (schemaKey.isAlbedoMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::METALLIC_MAP_BIT: + if (schemaKey.isMetallicMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); + } + break; + case graphics::MaterialKey::ROUGHNESS_MAP_BIT: + if (schemaKey.isRoughnessMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::NORMAL_MAP_BIT: + if (schemaKey.isNormalMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); + } + break; + case graphics::MaterialKey::OCCLUSION_MAP_BIT: + if (schemaKey.isOcclusionMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::SCATTERING_MAP_BIT: + if (schemaKey.isScatteringMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::EMISSIVE_MAP_BIT: + if (schemaKey.isEmissiveMap() && !schemaKey.isLightmapMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); + } + break; + case graphics::MaterialKey::LIGHTMAP_MAP_BIT: + if (schemaKey.isLightmapMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); + } + break; + default: + break; + } + } + + schema._key = (uint32_t)schemaKey._flags.to_ulong(); + schemaBuffer.edit() = schema; + multiMaterial.setNeedsUpdate(false); +} + +void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) { + if (multiMaterial.size() == 0) { + return; + } + + if (multiMaterial.needsUpdate() || multiMaterial.areTexturesLoading()) { + updateMultiMaterial(multiMaterial); + } + auto textureCache = DependencyManager::get(); - batch.setUniformBuffer(gr::Buffer::Material, material->getSchemaBuffer()); + static gpu::TextureTablePointer defaultMaterialTextures = std::make_shared(); + static std::once_flag once; + std::call_once(once, [textureCache] { + defaultMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); + // MaterialEmissiveLightmap has to be set later + }); - const auto& materialKey = material->getKey(); - const auto& textureMaps = material->getTextureMaps(); - - int numUnlit = 0; - if (materialKey.isUnlit()) { - numUnlit++; - } - - const auto& drawMaterialTextures = material->getTextureTable(); - - // Albedo - if (materialKey.isAlbedoMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); + auto& schemaBuffer = multiMaterial.getSchemaBuffer(); + batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer); + if (enableTextures) { + batch.setResourceTextureTable(multiMaterial.getTextureTable()); + } else { + auto key = multiMaterial.getMaterialKey(); + if (key.isLightmapMap()) { + defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); + } else if (key.isEmissiveMap()) { + defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); } + batch.setResourceTextureTable(defaultMaterialTextures); } - - // Roughness map - if (materialKey.isRoughnessMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); - } - } - - // Normal map - if (materialKey.isNormalMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); - } - } - - // Metallic map - if (materialKey.isMetallicMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); - } - } - - // Occlusion map - if (materialKey.isOcclusionMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); - } - } - - // Scattering map - if (materialKey.isScatteringMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); - } - } - - // Emissive / Lightmap - if (materialKey.isLightmapMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); - - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); - } - } else if (materialKey.isEmissiveMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); - } - } - - batch.setResourceTextureTable(material->getTextureTable()); } diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h index b7d22bc72d..0f3d1160ef 100644 --- a/libraries/render-utils/src/RenderPipelines.h +++ b/libraries/render-utils/src/RenderPipelines.h @@ -15,7 +15,9 @@ class RenderPipelines { public: - static void bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures); + static void bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures); + static void updateMultiMaterial(graphics::MultiMaterial& multiMaterial); + static void bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures); }; diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 556e305fac..7217a3e5eb 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -42,7 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { auto program = gpu::Shader::createProgram(shader::gpu::program::drawNothing); auto state = std::make_shared(); drawMask(*state); - state->setColorWriteMask(0); + state->setColorWriteMask(gpu::State::WRITE_NONE); _meshStencilPipeline = gpu::Pipeline::create(program, state); } @@ -54,7 +54,7 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { auto program = gpu::Shader::createProgram(shader::render_utils::program::stencil_drawMask); auto state = std::make_shared(); drawMask(*state); - state->setColorWriteMask(0); + state->setColorWriteMask(gpu::State::WRITE_NONE); _paintStencilPipeline = gpu::Pipeline::create(program, state); } diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 87da47a27a..103782bd3f 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -44,7 +44,7 @@ const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f; // Used when avatar is missing joints... (avatar space) const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; -const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.06f, -0.09f }; +const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.064f, 0.084f }; const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index b983213e4a..90d7c71c9f 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -70,12 +70,12 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u } FileStorage::FileStorage(const QString& filename) : _file(filename) { - bool opened = _file.open(QFile::ReadWrite); + bool opened = _file.open(QFile::ReadWrite | QFile::Unbuffered); if (opened) { _hasWriteAccess = true; } else { _hasWriteAccess = false; - opened = _file.open(QFile::ReadOnly); + opened = _file.open(QFile::ReadOnly| QFile::Unbuffered); } if (opened) { diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index f10aba7920..a67e3127e5 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "OculusHelpers.h" @@ -30,7 +31,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { return false; } - if (ovr::quitRequested(status) || ovr::displayLost(status) || !ovr::handleOVREvents()) { + if (ovr::quitRequested(status) || ovr::displayLost(status)) { QMetaObject::invokeMethod(qApp, "quit"); return false; } diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 547d3ee5fe..1abb7cdad7 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -13,6 +13,9 @@ #include +#define OVRPL_DISABLED +#include + class OculusBaseDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: @@ -30,7 +33,7 @@ public: QRectF getPlayAreaRect() override; QVector getSensorPositions() override; - + protected: void customizeContext() override; void uncustomizeContext() override; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 76ff4a1755..c2b9145f3c 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -327,8 +328,14 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime, ovr::toGlm(headPose.AngularVelocity)); glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * - inputCalibrationData.defaultHeadMat; + glm::mat4 defaultHeadOffset; + if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { + // align the eyes of the user with the eyes of the avatar + defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + } else { + // align the head of the user with the head of the avatar + defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); + } pose.valid = true; _poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar); diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 548afb97ab..2693b9ee7e 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -296,34 +296,3 @@ controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, pose.valid = true; return pose; } - -bool hifi::ovr::handleOVREvents() { -#ifdef OCULUS_APP_ID - if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { - // pop messages to see if we got a return for an entitlement check - ovrMessageHandle message = ovr_PopMessage(); - - while (message) { - switch (ovr_Message_GetType(message)) { - case ovrMessage_Entitlement_GetIsViewerEntitled: { - if (!ovr_Message_IsError(message)) { - // this viewer is entitled, no need to flag anything - qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally"; - } else { - // we failed the entitlement check, quit - qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID; - return false; - } - } - } - - // free the message handle to cleanup and not leak - ovr_FreeMessage(message); - - // pop the next message to check, if there is one - message = ovr_PopMessage(); - } - } -#endif - return true; -} diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index bdfc4434bb..3587117825 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -30,7 +30,6 @@ struct ovr { static ovrSessionStatus getStatus(ovrResult& result); static ovrTrackingState getTrackingState(double absTime = 0.0, ovrBool latencyMarker = ovrFalse); static QString getError(); - static bool handleOVREvents(); static inline bool quitRequested() { return quitRequested(getStatus()); } static inline bool reorientRequested() { return reorientRequested(getStatus()); } diff --git a/plugins/oculus/src/OculusPlatformPlugin.cpp b/plugins/oculus/src/OculusPlatformPlugin.cpp new file mode 100644 index 0000000000..27fb98c8b5 --- /dev/null +++ b/plugins/oculus/src/OculusPlatformPlugin.cpp @@ -0,0 +1,109 @@ +// +// Created by Wayne Chen on 2019/01/08 +// Copyright 2019 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 "OculusPlatformPlugin.h" + +#include + +#include + +#include "OculusHelpers.h" + +QString OculusAPIPlugin::NAME { "Oculus Rift" }; + +OculusAPIPlugin::OculusAPIPlugin() { + _session = hifi::ovr::acquireRenderSession(); +} + +OculusAPIPlugin::~OculusAPIPlugin() { + hifi::ovr::releaseRenderSession(_session); +} + +bool OculusAPIPlugin::isRunning() const { + return (qApp->property(hifi::properties::OCULUS_STORE).toBool()); +} + +void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) { +#ifdef OCULUS_APP_ID + _nonceUserIDCallback = callback; + ovr_User_GetUserProof(); + ovr_User_GetLoggedInUser(); +#endif +} + +void OculusAPIPlugin::handleOVREvents() { +#ifdef OCULUS_APP_ID + if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { + // pop messages to see if we got a return for an entitlement check + ovrMessageHandle message { nullptr }; + + // pop the next message to check, if there is one + while ((message = ovr_PopMessage())) { + switch (ovr_Message_GetType(message)) { + case ovrMessage_Entitlement_GetIsViewerEntitled: { + if (!ovr_Message_IsError(message)) { + // this viewer is entitled, no need to flag anything + qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally"; + } else { + // we failed the entitlement check, quit + qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID; + QMetaObject::invokeMethod(qApp, "quit"); + } + break; + } + case ovrMessage_User_Get: { + if (!ovr_Message_IsError(message)) { + qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded"; + ovrUserHandle user = ovr_Message_GetUser(message); + _user = ovr_User_GetOculusID(user); + // went all the way through the `requestNonceAndUserID()` pipeline successfully. + } else { + qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message))); + // emit the signal so we don't hang for it anywhere else. + _user = ""; + } + break; + } + case ovrMessage_User_GetLoggedInUser: { + if (!ovr_Message_IsError(message)) { + ovrUserHandle user = ovr_Message_GetUser(message); + _userID = ovr_User_GetID(user); + ovr_User_Get(_userID); + } else { + qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message))); + // emit the signal so we don't hang for it anywhere else. + } + _userIDChanged = true; + break; + } + case ovrMessage_User_GetUserProof: { + if (!ovr_Message_IsError(message)) { + ovrUserProofHandle userProof = ovr_Message_GetUserProof(message); + _nonce = ovr_UserProof_GetNonce(userProof); + qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce; + } else { + qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message))); + _nonce = ""; + // emit the signal so we don't hang for it anywhere else. + } + _nonceChanged = true; + break; + } + } + + if (_nonceChanged && _userIDChanged) { + _nonceUserIDCallback(_nonce, QString::number(_userID)); + _nonceChanged = _userIDChanged = false; + } + + // free the message handle to cleanup and not leak + ovr_FreeMessage(message); + } + } +#endif +} diff --git a/plugins/oculus/src/OculusPlatformPlugin.h b/plugins/oculus/src/OculusPlatformPlugin.h new file mode 100644 index 0000000000..3d80540419 --- /dev/null +++ b/plugins/oculus/src/OculusPlatformPlugin.h @@ -0,0 +1,39 @@ +// +// Created by Wayne Chen on 2019/01/08 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +#include + +#define OVRPL_DISABLED +#include + +class OculusAPIPlugin : public OculusPlatformPlugin { +public: + OculusAPIPlugin(); + virtual ~OculusAPIPlugin(); + QString getName() const { return NAME; } + QString getOculusUserID() const { return _user; }; + + bool isRunning() const; + + virtual void requestNonceAndUserID(NonceUserIDCallback callback); + + virtual void handleOVREvents(); + +private: + static QString NAME; + NonceUserIDCallback _nonceUserIDCallback; + QString _nonce; + bool _nonceChanged{ false }; + bool _userIDChanged{ false }; + QString _user; + ovrID _userID; + ovrSession _session; +}; diff --git a/plugins/oculus/src/OculusProvider.cpp b/plugins/oculus/src/OculusProvider.cpp index 47ccc5304e..67871b8610 100644 --- a/plugins/oculus/src/OculusProvider.cpp +++ b/plugins/oculus/src/OculusProvider.cpp @@ -18,15 +18,18 @@ #include "OculusDisplayPlugin.h" #include "OculusDebugDisplayPlugin.h" +#include "OculusPlatformPlugin.h" #include "OculusControllerManager.h" -class OculusProvider : public QObject, public DisplayProvider, InputProvider +class OculusProvider : public QObject, public DisplayProvider, InputProvider, OculusPlatformProvider { Q_OBJECT Q_PLUGIN_METADATA(IID DisplayProvider_iid FILE "oculus.json") Q_INTERFACES(DisplayProvider) Q_PLUGIN_METADATA(IID InputProvider_iid FILE "oculus.json") Q_INTERFACES(InputProvider) + Q_PLUGIN_METADATA(IID OculusPlatformProvider_iid FILE "oculus.json") + Q_INTERFACES(OculusPlatformProvider) public: OculusProvider(QObject* parent = nullptr) : QObject(parent) {} @@ -62,6 +65,15 @@ public: return _inputPlugins; } + virtual OculusPlatformPluginPointer getOculusPlatformPlugin() override { + static std::once_flag once; + std::call_once(once, [&] { + _oculusPlatformPlugin = std::make_shared(); + + }); + return _oculusPlatformPlugin; + } + virtual void destroyInputPlugins() override { _inputPlugins.clear(); } @@ -73,6 +85,7 @@ public: private: DisplayPluginList _displayPlugins; InputPluginList _inputPlugins; + OculusPlatformPluginPointer _oculusPlatformPlugin; }; #include "OculusProvider.moc" diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 3aea5f1ce0..34ebb73fda 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1024,7 +1025,16 @@ void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::I //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity); - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + + glm::mat4 defaultHeadOffset; + if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { + // align the eyes of the user with the eyes of the avatar + defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + } else { + // align the head of the user with the head of the avatar + defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); + } + glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; _poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar); } diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 9737fb7f1a..aef533b7a3 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -14,7 +14,6 @@ import QtQuick.Layouts 1.3 import stylesUit 1.0 import controlsUit 1.0 as HifiControls import "configSlider" -import "../lib/jet/qml" as Jet Rectangle { HifiConstants { id: hifi;} @@ -249,12 +248,6 @@ Rectangle { checked: render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] = checked } } - HifiControls.CheckBox { - boxSize: 20 - text: "Transparents in HUD" - checked: render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] - onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked } - } } Column { @@ -277,6 +270,12 @@ Rectangle { checked: render.mainViewTask.getConfig("DrawZones")["enabled"] onCheckedChanged: { render.mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; render.mainViewTask.getConfig("DrawZones")["enabled"] = checked; } } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents in HUD" + checked: render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked } + } } } Separator {} @@ -303,5 +302,13 @@ Rectangle { } } } + Row { + HifiControls.Button { + text: "Material" + onClicked: { + sendToScript({method: "openMaterialInspectorView"}); + } + } + } } } diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js index cffeb615c9..bae5c4646d 100644 --- a/scripts/developer/utilities/render/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -12,6 +12,8 @@ (function() { var AppUi = Script.require('appUi'); + + var MaterialInspector = Script.require('./materialInspector.js'); var moveDebugCursor = false; var onMousePressEvent = function (e) { @@ -41,11 +43,12 @@ Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 }; } - function Page(title, qmlurl, width, height) { + function Page(title, qmlurl, width, height, handleWindowFunc) { this.title = title; this.qml = qmlurl; this.width = width; this.height = height; + this.handleWindowFunc = handleWindowFunc; this.window; @@ -73,8 +76,10 @@ presentationMode: Desktop.PresentationMode.NATIVE, size: {x: this.width, y: this.height} }); + this.handleWindowFunc(this.window); this.window.closed.connect(function () { that.killView(); + this.handleWindowFunc(undefined); }); } }; @@ -84,8 +89,12 @@ this._pages = {}; }; - Pages.prototype.addPage = function (command, title, qmlurl, width, height) { - this._pages[command] = new Page(title, qmlurl, width, height); + Pages.prototype.addPage = function (command, title, qmlurl, width, height, handleWindowFunc) { + if (handleWindowFunc === undefined) { + // Workaround for bad linter + handleWindowFunc = function(window){}; + } + this._pages[command] = new Page(title, qmlurl, width, height, handleWindowFunc); }; Pages.prototype.open = function (command) { @@ -110,6 +119,7 @@ pages.addPage('openEngineView', 'Render Engine', 'engineInspector.qml', 300, 400); pages.addPage('openEngineLODView', 'Render LOD', 'lod.qml', 300, 400); pages.addPage('openCullInspectorView', 'Cull Inspector', 'culling.qml', 300, 400); + pages.addPage('openMaterialInspectorView', 'Material Inspector', 'materialInspector.qml', 300, 400, MaterialInspector.setWindow); function fromQml(message) { if (pages.open(message.method)) { diff --git a/scripts/developer/utilities/render/materialInspector.js b/scripts/developer/utilities/render/materialInspector.js new file mode 100644 index 0000000000..76e5da5cd0 --- /dev/null +++ b/scripts/developer/utilities/render/materialInspector.js @@ -0,0 +1,165 @@ +// +// materialInspector.js +// +// Created by Sabrina Shanman on 2019-01-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +"use strict"; + +var activeWindow; + +// Adapted from Samuel G's material painting script +function getTopMaterial(multiMaterial) { + // For non-models: multiMaterial[0] will be the top material + // For models, multiMaterial[0] is the base material, and multiMaterial[1] is the highest priority applied material + if (multiMaterial.length > 1) { + if (multiMaterial[1].priority > multiMaterial[0].priority) { + return multiMaterial[1]; + } + } + + return multiMaterial[0]; +} + +function updateMaterial(type, id, meshPart) { + var mesh = Graphics.getModel(id); + var meshPartString = meshPart.toString(); + if (!mesh) { + return; + } + var materials = mesh.materialLayers; + if (!materials[meshPartString] || materials[meshPartString].length <= 0) { + return; + } + + var topMaterial = getTopMaterial(materials[meshPartString]); + var materialJSONText = JSON.stringify({ + materialVersion: 1, + materials: topMaterial.material + }, null, 2); + + toQml({method: "setObjectInfo", params: {id: id, type: type, meshPart: meshPart}}); + toQml({method: "setMaterialJSON", params: {materialJSONText: materialJSONText}}); +} + +// Adapted from Samuel G's material painting script +function getHoveredMaterialLocation(event) { + var pickRay = Camera.computePickRay(event.x, event.y); + var closest; + var id; + var type = "Entity"; + var avatar = AvatarManager.findRayIntersection(pickRay); + var entity = Entities.findRayIntersection(pickRay, true); + var overlay = Overlays.findRayIntersection(pickRay, true); + + closest = entity; + id = entity.entityID; + + if (avatar.intersects && avatar.distance < closest.distance) { + closest = avatar; + id = avatar.avatarID; + type = "Avatar"; + } else if (overlay.intersects && overlay.distance < closest.distance) { + closest = overlay; + id = overlay.overlayID; + type = "Overlay"; + } + + if (closest.intersects) { + return { + type: type, + id: id, + meshPart: (closest.extraInfo.shapeID ? closest.extraInfo.shapeID : 0) + }; + } else { + return undefined; + } +} + +var pressedID; +var pressedMeshPart; + +function mousePressEvent(event) { + if (!event.isLeftButton) { + return; + } + + var result = getHoveredMaterialLocation(event); + + if (result !== undefined) { + pressedID = result.id; + pressedMeshPart = result.meshPart; + } +} + +function mouseReleaseEvent(event) { + if (!event.isLeftButton) { + return; + } + + var result = getHoveredMaterialLocation(event); + + if (result !== undefined && result.id === pressedID && result.meshPart === pressedMeshPart) { + updateMaterial(result.type, result.id, result.meshPart); + setSelectedObject(result.id, result.type); + } +} + +function killWindow() { + setWindow(undefined); +} + +function toQml(message) { + if (activeWindow === undefined) { + return; // Shouldn't happen + } + + activeWindow.sendToQml(message); +} + +function fromQml(message) { + // No cases currently +} + +var SELECT_LIST = "luci_materialInspector_SelectionList"; +Selection.enableListHighlight(SELECT_LIST, { + outlineUnoccludedColor: { red: 255, green: 255, blue: 255 } +}); +function setSelectedObject(id, type) { + Selection.clearSelectedItemsList(SELECT_LIST); + if (id !== undefined && !Uuid.isNull(id)) { + Selection.addToSelectedItemsList(SELECT_LIST, type.toLowerCase(), id); + } +} + +function setWindow(window) { + if (activeWindow !== undefined) { + setSelectedObject(Uuid.NULL, ""); + activeWindow.closed.disconnect(killWindow); + activeWindow.fromQml.disconnect(fromQml); + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + activeWindow.close(); + } + if (window !== undefined) { + window.closed.connect(killWindow); + window.fromQml.connect(fromQml); + Controller.mousePressEvent.connect(mousePressEvent); + Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + } + activeWindow = window; +} + +function cleanup() { + setWindow(undefined); + Selection.disableListHighlight(SELECT_LIST); +} + +Script.scriptEnding.connect(cleanup); + +module.exports = { + setWindow: setWindow +}; diff --git a/scripts/developer/utilities/render/materialInspector.qml b/scripts/developer/utilities/render/materialInspector.qml new file mode 100644 index 0000000000..d4dad203cd --- /dev/null +++ b/scripts/developer/utilities/render/materialInspector.qml @@ -0,0 +1,65 @@ +// +// materialInspector.qml +// +// Created by Sabrina Shanman on 2019-01-16 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.3 as Original +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + +Rectangle { + HifiConstants { id: hifi;} + color: Qt.rgba(hifi.colors.baseGray.r, hifi.colors.baseGray.g, hifi.colors.baseGray.b, 0.8); + id: root; + + function fromScript(message) { + switch (message.method) { + case "setObjectInfo": + entityIDInfo.text = "Type: " + message.params.type + "\nID: " + message.params.id + "\nMesh Part: " + message.params.meshPart; + break; + case "setMaterialJSON": + materialJSONText.text = message.params.materialJSONText; + break; + } + } + + Rectangle { + id: entityIDContainer + height: 52 + width: root.width + color: Qt.rgba(root.color.r * 0.7, root.color.g * 0.7, root.color.b * 0.7, 0.8); + TextEdit { + id: entityIDInfo + text: "Type: Unknown\nID: None\nMesh Part: Unknown" + font.pointSize: 9 + color: "#FFFFFF" + readOnly: true + selectByMouse: true + } + } + + Original.ScrollView { + anchors.top: entityIDContainer.bottom + height: root.height - entityIDContainer.height + width: root.width + clip: true + Original.ScrollBar.horizontal.policy: Original.ScrollBar.AlwaysOff + TextEdit { + id: materialJSONText + text: "Click an object to get material JSON" + width: root.width + font.pointSize: 10 + color: "#FFFFFF" + readOnly: true + selectByMouse: true + wrapMode: Text.WordWrap + } + } +} diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index cb194c9d66..4c78da7306 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -39,6 +39,14 @@ "leftMargin": { "tooltip": "The left margin, in meters." }, + "zoneShapeType": { + "tooltip": "The shape of the volume in which the zone's lighting effects and avatar permissions have effect.", + "jsPropertyName": "shapeType" + }, + "zoneCompoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, "flyingAllowed": { "tooltip": "If enabled, users can fly in the zone." }, @@ -133,7 +141,7 @@ "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." }, "compoundShapeURL": { - "tooltip": "The OBJ file to use for the compound shape if Collision Shape is \"compound\"." + "tooltip": "The model file to use for the compound shape if Collision Shape is \"Compound\"." }, "animation.url": { "tooltip": "An animation to play on the model." @@ -362,6 +370,9 @@ "priority": { "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." }, + "materialMappingMode": { + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + }, "materialMappingPos": { "tooltip": "The offset position of the bottom left of the material within the parent's UV space." }, @@ -371,6 +382,9 @@ "materialMappingRot": { "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." }, + "materialRepeat": { + "tooltip": "If enabled, the material will repeat, otherwise it will clamp." + }, "followCamera": { "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." }, diff --git a/scripts/system/assets/images/icon-zone.svg b/scripts/system/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/system/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/system/assets/images/materials/GridPattern.json b/scripts/system/assets/images/materials/GridPattern.json new file mode 100644 index 0000000000..468b709ea4 --- /dev/null +++ b/scripts/system/assets/images/materials/GridPattern.json @@ -0,0 +1,13 @@ +{ + "materialVersion": 1, + "materials": { + "albedo": [ + 0.0, + 0.0, + 7.0 + ], + "unlit": true, + "opacity": 0.4, + "albedoMap": "GridPattern.png" + } +} diff --git a/scripts/system/assets/images/materials/GridPattern.png b/scripts/system/assets/images/materials/GridPattern.png new file mode 100644 index 0000000000..2ecc7f8570 Binary files /dev/null and b/scripts/system/assets/images/materials/GridPattern.png differ diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 631c0e03e8..fb61b914a3 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -62,6 +62,7 @@ function getMyAvatar() { function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), + hmdAvatarAlignmentType: MyAvatar.getHmdAvatarAlignmentType(), collisionsEnabled: MyAvatar.getCollisionsEnabled(), otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, @@ -129,6 +130,13 @@ function onDominantHandChanged(dominantHand) { } } +function onHmdAvatarAlignmentTypeChanged(type) { + if (currentAvatarSettings.hmdAvatarAlignmentType !== type) { + currentAvatarSettings.hmdAvatarAlignmentType = type; + sendToQml({'method' : 'settingChanged', 'name' : 'hmdAvatarAlignmentType', 'value' : type}); + } +} + function onCollisionsEnabledChanged(enabled) { if(currentAvatarSettings.collisionsEnabled !== enabled) { currentAvatarSettings.collisionsEnabled = enabled; @@ -331,6 +339,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See currentAvatar.avatarScale = message.avatarScale; MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setHmdAvatarAlignmentType(message.settings.hmdAvatarAlignmentType); MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; @@ -521,6 +530,7 @@ function off() { Entities.deletingWearable.disconnect(onDeletingWearable); MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.disconnect(onHmdAvatarAlignmentTypeChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); @@ -542,6 +552,7 @@ function on() { Entities.deletingWearable.connect(onDeletingWearable); MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.connect(onHmdAvatarAlignmentTypeChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 84143f4c25..2e175d72a4 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -82,13 +82,18 @@ var selectionManager = SelectionManager; var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); -var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); if (properties.type === 'Light') { return { url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; } else { return { url: PARTICLE_SYSTEM_URL, @@ -106,11 +111,15 @@ var gridTool = new GridTool({ }); gridTool.setVisible(false); +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); }); var DEGREES_TO_RADIANS = Math.PI / 180.0; @@ -836,7 +845,7 @@ var toolBar = (function () { dialogWindow.fromQml.connect(fromQml); } }; - }; + } addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); @@ -1492,6 +1501,7 @@ Script.scriptEnding.connect(function () { cleanupModelMenus(); tooltip.cleanup(); selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); Entities.setLightsArePickable(originalLightsArePickable); Overlays.deleteOverlay(importingSVOImageOverlay); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index fdf0993235..f059b91e81 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -44,6 +44,7 @@ const COLUMNS = { initialWidth: 0.16, initiallyShown: true, alwaysShown: true, + defaultSortOrder: ASCENDING_SORT, }, name: { columnHeader: "Name", @@ -51,6 +52,7 @@ const COLUMNS = { initialWidth: 0.34, initiallyShown: true, alwaysShown: true, + defaultSortOrder: ASCENDING_SORT, }, url: { columnHeader: "File", @@ -58,6 +60,7 @@ const COLUMNS = { propertyID: "url", initialWidth: 0.34, initiallyShown: true, + defaultSortOrder: ASCENDING_SORT, }, locked: { columnHeader: "", @@ -66,6 +69,7 @@ const COLUMNS = { initialWidth: 0.08, initiallyShown: true, alwaysShown: true, + defaultSortOrder: DESCENDING_SORT, }, visible: { columnHeader: "", @@ -74,25 +78,29 @@ const COLUMNS = { initialWidth: 0.08, initiallyShown: true, alwaysShown: true, + defaultSortOrder: DESCENDING_SORT, }, verticesCount: { columnHeader: "Verts", dropdownLabel: "Vertices", propertyID: "verticesCount", initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, }, texturesCount: { columnHeader: "Texts", dropdownLabel: "Textures", propertyID: "texturesCount", initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, }, texturesSize: { columnHeader: "Text MB", dropdownLabel: "Texture Size", propertyID: "texturesSize", initialWidth: 0.10, - format: decimalMegabytes + format: decimalMegabytes, + defaultSortOrder: DESCENDING_SORT, }, hasTransparent: { columnHeader: "", @@ -100,6 +108,7 @@ const COLUMNS = { dropdownLabel: "Transparency", propertyID: "hasTransparent", initialWidth: 0.04, + defaultSortOrder: DESCENDING_SORT, }, isBaked: { columnHeader: "", @@ -107,12 +116,14 @@ const COLUMNS = { dropdownLabel: "Baked", propertyID: "isBaked", initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, }, drawCalls: { columnHeader: "Draws", dropdownLabel: "Draws", propertyID: "drawCalls", initialWidth: 0.08, + defaultSortOrder: DESCENDING_SORT, }, hasScript: { columnHeader: "k", @@ -120,27 +131,10 @@ const COLUMNS = { dropdownLabel: "Script", propertyID: "hasScript", initialWidth: 0.06, + defaultSortOrder: DESCENDING_SORT, }, }; -const COMPARE_ASCENDING = function(a, b) { - let va = a[currentSortColumnID]; - let vb = b[currentSortColumnID]; - - if (va < vb) { - return -1; - } else if (va > vb) { - return 1; - } else if (a.id < b.id) { - return -1; - } - - return 1; -}; -const COMPARE_DESCENDING = function(a, b) { - return COMPARE_ASCENDING(b, a); -}; - const FILTER_TYPES = [ "Shape", "Model", @@ -643,6 +637,10 @@ function loaded() { refreshEntityList(); } + + const isNullOrEmpty = function(value) { + return value === undefined || value === null || value === ""; + }; function refreshEntityList() { PROFILE("refresh-entity-list", function() { @@ -660,8 +658,31 @@ function loaded() { }); PROFILE("sort", function() { - let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING; - visibleEntities.sort(cmp); + let isAscendingSort = currentSortOrder === ASCENDING_SORT; + let isDefaultSort = currentSortOrder === COLUMNS[currentSortColumnID].defaultSortOrder; + visibleEntities.sort((entityA, entityB) => { + /** + * If the default sort is ascending, empty should be considered largest. + * If the default sort is descending, empty should be considered smallest. + */ + if (!isAscendingSort) { + [entityA, entityB] = [entityB, entityA]; + } + let valueA = entityA[currentSortColumnID]; + let valueB = entityB[currentSortColumnID]; + + if (valueA === valueB) { + return entityA.id < entityB.id ? -1 : 1; + } + + if (isNullOrEmpty(valueA)) { + return (isDefaultSort ? 1 : -1) * (isAscendingSort ? 1 : -1); + } + if (isNullOrEmpty(valueB)) { + return (isDefaultSort ? -1 : 1) * (isAscendingSort ? 1 : -1); + } + return valueA < valueB ? -1 : 1; + }); }); PROFILE("update-dom", function() { @@ -757,7 +778,7 @@ function loaded() { } else { elSortOrders[currentSortColumnID].innerHTML = ""; currentSortColumnID = columnID; - currentSortOrder = ASCENDING_SORT; + currentSortOrder = COLUMNS[currentSortColumnID].defaultSortOrder; } refreshSortOrder(); refreshEntityList(); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8b7264eeb1..ee95312fa4 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -184,6 +184,20 @@ const GROUPS = [ id: "zone", addToGroup: "base", properties: [ + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, + propertyID: "zoneShapeType", + propertyName: "shapeType", // actual entity property name + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "zoneCompoundShapeURL", + propertyName: "compoundShapeURL", // actual entity property name + }, { label: "Flying Allowed", type: "bool", @@ -1345,24 +1359,6 @@ const GROUPS = [ }, ] }, - { - id: "zone_shape", - label: "ZONE SHAPE", - properties: [ - { - label: "Shape Type", - type: "dropdown", - options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", - "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, - propertyID: "shapeType", - }, - { - label: "Compound Shape URL", - type: "string", - propertyID: "compoundShapeURL", - }, - ] - }, { id: "physics", label: "PHYSICS", @@ -1454,7 +1450,7 @@ const GROUPS_PER_TYPE = { None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], - Zone: [ 'base', 'zone', 'spatial', 'behavior', 'zone_shape', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ], Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], @@ -3317,7 +3313,8 @@ function loaded() { } } - let doSelectElement = lastEntityID === '"' + selectedEntityProperties.id + '"'; + let hasSelectedEntityChanged = lastEntityID !== '"' + selectedEntityProperties.id + '"'; + let doSelectElement = !hasSelectedEntityChanged; // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + selectedEntityProperties.id + '"'; @@ -3437,7 +3434,7 @@ function loaded() { property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; - if ($(property.elColorPicker).attr('active') === 'true') { + if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') { // Set the color picker inactive before setting the color, // otherwise an update will be sent directly after setting it here. $(property.elColorPicker).attr('active', 'false'); @@ -3801,6 +3798,11 @@ function loaded() { if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { return; } + + if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) { + return; + } + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js new file mode 100644 index 0000000000..fe950c2e2b --- /dev/null +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -0,0 +1,255 @@ +"use strict"; + +// entityShapeVisualizer.js +// +// Created by Thijs Wenker on 1/11/19 +// +// Copyright 2019 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 +// + +var SHAPETYPE_TO_SHAPE = { + "box": "Cube", + "ellipsoid": "Sphere", + "cylinder-y": "Cylinder", +}; + +var REQUESTED_ENTITY_SHAPE_PROPERTIES = [ + 'type', 'shapeType', 'compoundShapeURL', 'localDimensions' +]; + +function getEntityShapePropertiesForType(properties) { + switch (properties.type) { + case "Zone": + if (SHAPETYPE_TO_SHAPE[properties.shapeType]) { + return { + type: "Shape", + shape: SHAPETYPE_TO_SHAPE[properties.shapeType], + localDimensions: properties.localDimensions + }; + } else if (properties.shapeType === "compound") { + return { + type: "Model", + modelURL: properties.compoundShapeURL, + localDimensions: properties.localDimensions + }; + } else if (properties.shapeType === "sphere") { + var sphereDiameter = Math.max(properties.localDimensions.x, properties.localDimensions.y, + properties.localDimensions.z); + return { + type: "Sphere", + modelURL: properties.compoundShapeURL, + localDimensions: {x: sphereDiameter, y: sphereDiameter, z: sphereDiameter} + }; + } + break; + } + + // Default properties + return { + type: "Shape", + shape: "Cube", + localDimensions: properties.localDimensions + }; +} + +function deepEqual(a, b) { + if (a === b) { + return true; + } + + if (typeof(a) !== "object" || typeof(b) !== "object") { + return false; + } + + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + + for (var property in a) { + if (!a.hasOwnProperty(property)) { + continue; + } + if (!b.hasOwnProperty(property)) { + return false; + } + if (!deepEqual(a[property], b[property])) { + return false; + } + } + return true; +} + +/** + * Returns an array of property names which are different in comparison. + * @param propertiesA + * @param propertiesB + * @returns {Array} - array of different property names + */ +function compareEntityProperties(propertiesA, propertiesB) { + var differentProperties = [], + property; + + for (property in propertiesA) { + if (!propertiesA.hasOwnProperty(property)) { + continue; + } + if (!propertiesB.hasOwnProperty(property) || !deepEqual(propertiesA[property], propertiesB[property])) { + differentProperties.push(property); + } + } + for (property in propertiesB) { + if (!propertiesB.hasOwnProperty(property)) { + continue; + } + if (!propertiesA.hasOwnProperty(property)) { + differentProperties.push(property); + } + } + + return differentProperties; +} + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} + +function EntityShape(entityID) { + this.entityID = entityID; + var propertiesForType = getEntityShapePropertiesForType(Entities.getEntityProperties(entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES)); + + this.previousPropertiesForType = propertiesForType; + + this.initialize(propertiesForType); +} + +EntityShape.prototype = { + initialize: function(properties) { + // Create new instance of JS object: + var overlayProperties = deepCopy(properties); + + overlayProperties.localPosition = Vec3.ZERO; + overlayProperties.localRotation = Quat.IDENTITY; + overlayProperties.canCastShadows = false; + overlayProperties.parentID = this.entityID; + overlayProperties.collisionless = true; + this.entity = Entities.addEntity(overlayProperties, "local"); + var PROJECTED_MATERIALS = false; + this.materialEntity = Entities.addEntity({ + type: "Material", + localPosition: Vec3.ZERO, + localRotation: Quat.IDENTITY, + localDimensions: properties.localDimensions, + parentID: this.entity, + priority: 1, + materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", + materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), + }, "local"); + }, + update: function() { + var propertiesForType = getEntityShapePropertiesForType(Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES)); + + var difference = compareEntityProperties(propertiesForType, this.previousPropertiesForType); + + if (deepEqual(difference, ['localDimensions'])) { + this.previousPropertiesForType = propertiesForType; + Entities.editEntity(this.entity, { + localDimensions: propertiesForType.localDimensions, + }); + } else if (difference.length > 0) { + this.previousPropertiesForType = propertiesForType; + this.clear(); + this.initialize(propertiesForType); + } + }, + clear: function() { + Entities.deleteEntity(this.materialEntity); + Entities.deleteEntity(this.entity); + } +}; + +function EntityShapeVisualizer(visualizedTypes) { + this.acceptedEntities = []; + this.ignoredEntities = []; + this.entityShapes = {}; + + this.visualizedTypes = visualizedTypes; +} + +EntityShapeVisualizer.prototype = { + addEntity: function(entityID) { + if (this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID] = new EntityShape(entityID); + + }, + updateEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].update(); + }, + removeEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].clear(); + delete this.entityShapes[entityID]; + }, + cleanup: function() { + Object.keys(this.entityShapes).forEach(function(entityID) { + this.entityShapes[entityID].clear(); + }, this); + this.entityShapes = {}; + }, + setEntities: function(entities) { + var qualifiedEntities = entities.filter(function(entityID) { + if (this.acceptedEntities.indexOf(entityID) !== -1) { + return true; + } + if (this.ignoredEntities.indexOf(entityID) !== -1) { + return false; + } + if (this.visualizedTypes.indexOf(Entities.getEntityProperties(entityID, "type").type) !== -1) { + this.acceptedEntities.push(entityID); + return true; + } + this.ignoredEntities.push(entityID); + return false; + }, this); + + + var newEntries = []; + var updateEntries = []; + + var currentEntries = Object.keys(this.entityShapes); + qualifiedEntities.forEach(function(entityID) { + if (currentEntries.indexOf(entityID) !== -1) { + updateEntries.push(entityID); + } else { + newEntries.push(entityID); + } + }); + + var deleteEntries = currentEntries.filter(function(entityID) { + return updateEntries.indexOf(entityID) === -1; + }); + + deleteEntries.forEach(function(entityID) { + this.removeEntity(entityID); + }, this); + + updateEntries.forEach(function(entityID) { + this.updateEntity(entityID); + }, this); + + newEntries.forEach(function(entityID) { + this.addEntity(entityID); + }, this); + } +}; + +module.exports = EntityShapeVisualizer; diff --git a/server-console/package-lock.json b/server-console/package-lock.json deleted file mode 100644 index e27c3815f6..0000000000 --- a/server-console/package-lock.json +++ /dev/null @@ -1,2562 +0,0 @@ -{ - "name": "HighFidelitySandbox", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/node": { - "version": "8.10.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.29.tgz", - "integrity": "sha512-zbteaWZ2mdduacm0byELwtRyhYE40aK+pAanQk415gr1eRuu67x7QGOLmn8jz5zI8LDK7d0WI/oT6r5Trz4rzQ==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "always-tail": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/always-tail/-/always-tail-0.2.0.tgz", - "integrity": "sha1-M5sa9E1QJQqgeg6H7Mw6JOxET/4=", - "requires": { - "debug": "~0.7.2" - }, - "dependencies": { - "debug": { - "version": "0.7.4", - "resolved": "http://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" - } - } - }, - "ansi-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz", - "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=" - }, - "array-find-index": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz", - "integrity": "sha1-C8Jd2slB7IpJauJY/UrBiAA+868=", - "dev": true - }, - "asar": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/asar/-/asar-0.14.3.tgz", - "integrity": "sha512-+hNnVVDmYbv05We/a9knj/98w171+A94A9DNHj+3kXUr3ENTQoSEcfbJRvBBRHyOh4vukBYWujmHvvaMmQoQbg==", - "dev": true, - "requires": { - "chromium-pickle-js": "^0.2.0", - "commander": "^2.9.0", - "cuint": "^0.2.1", - "glob": "^6.0.4", - "minimatch": "^3.0.3", - "mkdirp": "^0.5.0", - "mksnapshot": "^0.3.0", - "tmp": "0.0.28" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "author-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", - "integrity": "sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", - "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", - "dev": true, - "requires": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - } - }, - "bl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", - "requires": { - "readable-stream": "~2.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - } - } - }, - "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", - "dev": true, - "requires": { - "traverse": ">=0.3.0 <0.4" - } - }, - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - } - }, - "chromium-pickle-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", - "integrity": "sha1-9psZLT99keOC5Lcb3bd4eGGasMY=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", - "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", - "dev": true - }, - "compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" - }, - "cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.0.1.tgz", - "integrity": "sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw==", - "requires": { - "ms": "^2.1.1" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decompress-zip": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.0.tgz", - "integrity": "sha1-rjvLfjTGWHmt/nfhnDD4ZgK0vbA=", - "dev": true, - "requires": { - "binary": "^0.3.0", - "graceful-fs": "^4.1.3", - "mkpath": "^0.1.0", - "nopt": "^3.0.1", - "q": "^1.1.2", - "readable-stream": "^1.1.8", - "touch": "0.0.3" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" - } - } - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "electron": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-3.0.0.tgz", - "integrity": "sha512-QN9X5vYa4kzJKniwhXlJwioX9qw2fDehdqxN/00KCLz/qnOz/IHLAHGikFjRwfEF2xnkmHxf61F8wn2LePPXXQ==", - "dev": true, - "requires": { - "@types/node": "^8.0.24", - "electron-download": "^4.1.0", - "extract-zip": "^1.0.3" - } - }, - "electron-download": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", - "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", - "dev": true, - "requires": { - "debug": "^3.0.0", - "env-paths": "^1.0.0", - "fs-extra": "^4.0.1", - "minimist": "^1.2.0", - "nugget": "^2.0.1", - "path-exists": "^3.0.0", - "rc": "^1.2.1", - "semver": "^5.4.1", - "sumchecker": "^2.0.2" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", - "dev": true - } - } - }, - "electron-log": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-1.1.1.tgz", - "integrity": "sha1-DboCXtM9DkW/j0DG6b487i+YbCg=" - }, - "electron-osx-sign": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz", - "integrity": "sha1-vk87ibKnWh3F8eckkIGrKSnKOiY=", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "compare-version": "^0.1.2", - "debug": "^2.6.8", - "isbinaryfile": "^3.0.2", - "minimist": "^1.2.0", - "plist": "^2.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "electron-packager": { - "version": "12.1.2", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.1.2.tgz", - "integrity": "sha512-7UiTNquZqhQm+L0Oqn7bR/7Ry/7zGO/PKwFpSNqHbWxydoN2aNahKyWjOPhcxHCAz+C1uu+tdyRe7wEN0BaJsA==", - "dev": true, - "requires": { - "asar": "^0.14.0", - "debug": "^3.0.0", - "electron-download": "^4.1.1", - "electron-osx-sign": "^0.4.1", - "extract-zip": "^1.0.3", - "fs-extra": "^5.0.0", - "galactus": "^0.2.1", - "get-package-info": "^1.0.0", - "nodeify": "^1.0.1", - "parse-author": "^2.0.0", - "pify": "^3.0.0", - "plist": "^2.0.0", - "rcedit": "^1.0.0", - "resolve": "^1.1.6", - "sanitize-filename": "^1.6.0", - "semver": "^5.3.0", - "yargs-parser": "^10.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "electron-download": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", - "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", - "dev": true, - "requires": { - "debug": "^3.0.0", - "env-paths": "^1.0.0", - "fs-extra": "^4.0.1", - "minimist": "^1.2.0", - "nugget": "^2.0.1", - "path-exists": "^3.0.0", - "rc": "^1.2.1", - "semver": "^5.4.1", - "sumchecker": "^2.0.2" - }, - "dependencies": { - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - } - } - }, - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, - "end-of-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", - "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=", - "requires": { - "once": "~1.3.0" - } - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" - }, - "env-paths": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", - "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", - "dev": true - }, - "error-ex": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz", - "integrity": "sha1-5ntD8+gsluo6WE/+4Ln8MyXYAtk=", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "extend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", - "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=" - }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "flora-colossus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.0.tgz", - "integrity": "sha1-VHKcNh7ezuAU3UQWeeGjfB13OkU=", - "dev": true, - "requires": { - "debug": "^3.1.0", - "fs-extra": "^4.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" - } - } - } - }, - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "dependencies": { - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "optional": true - } - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "galactus": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.1.tgz", - "integrity": "sha1-y+0tIKQMH1Z5o1kI4rlBVzPnjbk=", - "dev": true, - "requires": { - "debug": "^3.1.0", - "flora-colossus": "^1.0.0", - "fs-extra": "^4.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "get-package-info": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", - "integrity": "sha1-ZDJ5ZWPigRPNlHTbvQAFKYWkmZw=", - "dev": true, - "requires": { - "bluebird": "^3.1.1", - "debug": "^2.2.0", - "lodash.get": "^4.0.0", - "read-pkg-up": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "graceful-fs": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz", - "integrity": "sha1-kgM84RETxB4mKNYf36QLwQ3AFVw=" - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "hosted-git-info": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz", - "integrity": "sha1-2elTsmmIvogJbEbpJklNlgTDAPg=", - "dev": true - }, - "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - }, - "dependencies": { - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - } - } - }, - "inflight": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", - "integrity": "sha1-bLtFIevVHODsCpNr/XZX736bFyo=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" - }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-finite": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", - "integrity": "sha1-ZDhgPq6+J5OUj/SkJi7I2z1iWXs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-promise": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", - "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "requires": { - "buffer-alloc": "^1.2.0" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonfile": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.2.3.tgz", - "integrity": "sha1-4lK5mmr5AdPsQfMyWJyQUJp7xgU=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, - "lodash.merge": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, - "loud-rejection": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.3.0.tgz", - "integrity": "sha1-8omjkvF9K6rPGU0KZzAEOUQzsRU=", - "dev": true, - "requires": { - "array-find-index": "^1.0.0", - "signal-exit": "^2.1.2" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" - }, - "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", - "requires": { - "mime-db": "~1.36.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "mkpath": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz", - "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE=", - "dev": true - }, - "mksnapshot": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.1.tgz", - "integrity": "sha1-JQHAVldDbXQs6Vik/5LHfkDdN+Y=", - "dev": true, - "requires": { - "decompress-zip": "0.3.0", - "fs-extra": "0.26.7", - "request": "^2.79.0" - }, - "dependencies": { - "fs-extra": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node-notifier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", - "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", - "requires": { - "growly": "^1.3.0", - "semver": "^5.4.1", - "shellwords": "^0.1.1", - "which": "^1.3.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "nodeify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nodeify/-/nodeify-1.0.1.tgz", - "integrity": "sha1-ZKtpp7268DzhB7TwM1yHwLnpGx0=", - "dev": true, - "requires": { - "is-promise": "~1.0.0", - "promise": "~1.3.0" - } - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz", - "integrity": "sha1-jZJPFClg4Xd+f/4XBUNjHMfLAt8=", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "requires": { - "boolbase": "~1.0.0" - } - }, - "nugget": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", - "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", - "dev": true, - "requires": { - "debug": "^2.1.3", - "minimist": "^1.1.0", - "pretty-bytes": "^1.0.2", - "progress-stream": "^1.1.0", - "request": "^2.45.0", - "single-line-log": "^1.1.2", - "throttleit": "0.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", - "dev": true - } - } - }, - "number-is-nan": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz", - "integrity": "sha1-wCD1KcUoKt/dIz2R1LGBw9aG3Es=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz", - "integrity": "sha1-mVBEVsNZi1ytT8WcJuipuxB/4L0=", - "dev": true - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz", - "integrity": "sha1-DWK99EuRb9O73PLKsZGUj7CU8Ac=" - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parse-author": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", - "integrity": "sha1-00YL8d3Q367tQtp1QkLmX7aEqB8=", - "dev": true, - "requires": { - "author-regex": "^1.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", - "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "plist": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", - "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", - "dev": true, - "requires": { - "base64-js": "1.2.0", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - }, - "pretty-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", - "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.1.0" - } - }, - "process-nextick-args": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz", - "integrity": "sha1-D5awAc6pCxJZLOVm7bl+wR5pvQU=" - }, - "progress-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", - "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", - "dev": true, - "requires": { - "speedometer": "~0.1.2", - "through2": "~0.2.3" - } - }, - "promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-1.3.0.tgz", - "integrity": "sha1-5cyaTIJ45GZP/twBx9qEhCsEAXU=", - "dev": true, - "requires": { - "is-promise": "~1" - } - }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" - }, - "pump": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.1.tgz", - "integrity": "sha1-8fFAn7m9EIW721drQ7hOxLXq3Bo=", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "rcedit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.1.0.tgz", - "integrity": "sha512-JkXJ0IrUcdupLoIx6gE4YcFaMVSGtu7kQf4NJoDJUnfBZGuATmJ2Yal2v55KTltp+WV8dGr7A0RtOzx6jmtM6Q==", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - } - } - }, - "request-progress": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-1.0.2.tgz", - "integrity": "sha1-XUBvCBMJ32G0qKqDzVc032Pxi/U=", - "requires": { - "throttleit": "^1.0.0" - } - }, - "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sanitize-filename": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.1.tgz", - "integrity": "sha1-YS2hyWRz+gLczaktzVtKsWSmdyo=", - "dev": true, - "requires": { - "truncate-utf8-bytes": "^1.0.0" - } - }, - "semver": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", - "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" - }, - "signal-exit": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz", - "integrity": "sha1-N1h5sfkuvDszRIDQONxUam1VhWQ=", - "dev": true - }, - "single-line-log": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", - "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", - "dev": true, - "requires": { - "string-width": "^1.0.1" - } - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true, - "requires": { - "spdx-license-ids": "^1.0.2" - } - }, - "spdx-exceptions": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz", - "integrity": "sha1-IguEI5EZrpBFqJLbgag/TOFvgP0=", - "dev": true - }, - "spdx-expression-parse": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz", - "integrity": "sha1-1SsUtelnB3FECvIlvLVjEirEUvY=", - "dev": true, - "requires": { - "spdx-exceptions": "^1.0.4", - "spdx-license-ids": "^1.0.0" - } - }, - "spdx-license-ids": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.1.tgz", - "integrity": "sha1-0H6hek0v2TUfnZTi/5zsdBgP6PM=", - "dev": true - }, - "speedometer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", - "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", - "dev": true - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "string-width": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz", - "integrity": "sha1-ySEptvHX9SrPmvQkom44ZKBc6wo=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "sumchecker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", - "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "tar-fs": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", - "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", - "requires": { - "mkdirp": "^0.5.0", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - } - }, - "tar-stream": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.1.tgz", - "integrity": "sha1-UWx00b6j4THMC5NIkpyag/CirRE=", - "requires": { - "bl": "^1.0.0", - "end-of-stream": "^1.0.0", - "readable-stream": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - } - } - }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" - }, - "through2": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", - "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9", - "xtend": "~2.1.1" - }, - "dependencies": { - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, - "tmp": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", - "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "touch": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", - "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", - "dev": true, - "requires": { - "nopt": "~1.0.10" - }, - "dependencies": { - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - } - } - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", - "dev": true - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", - "dev": true, - "requires": { - "utf8-byte-length": "^1.0.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" - }, - "utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "~1.0.0", - "spdx-expression-parse": "~1.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "requires": { - "isexe": "^2.0.0" - } - }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" - }, - "wrap-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz", - "integrity": "sha1-fTD4+HP5pbvDpk2ryNF34HGuQm8=", - "requires": { - "string-width": "^1.0.1" - } - }, - "wrappy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz", - "integrity": "sha1-HmWWmWXMvC20VIxrhKbyxa7dRzk=" - }, - "xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", - "dev": true - }, - "xmldom": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", - "requires": { - "camelcase": "^2.0.1", - "cliui": "^3.0.3", - "decamelize": "^1.1.1", - "os-locale": "^1.4.0", - "string-width": "^1.0.1", - "window-size": "^0.1.4", - "y18n": "^3.2.0" - } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "~1.0.1" - } - } - } -} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ddf9a7b373..886f15ded4 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -6,31 +6,38 @@ if (NPM_EXECUTABLE) set_target_properties(jsdoc PROPERTIES FOLDER "Tools") endif() +function(check_test name) + set(RESULT TRUE) + if (BUILD_TOOLS_INCLUDE) + unset(RESULT) + list(FIND BUILD_TOOLS_INCLUDE ${name} BUILD_TOOL_FIND) + if (NOT (${BUILD_TOOL_FIND} EQUAL -1)) + set(RESULT TRUE) + endif() + endif() + set(BUILD_TOOL_RESULT ${RESULT} PARENT_SCOPE) +endfunction() + if (BUILD_TOOLS) - add_subdirectory(udt-test) - set_target_properties(udt-test PROPERTIES FOLDER "Tools") + set(ALL_TOOLS + udt-test + vhacd-util + frame-optimizer + gpu-frame-player + ice-client + ktx-tool + ac-client + skeleton-dump + atp-client + oven + nitpick + ) - add_subdirectory(vhacd-util) - set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") - - add_subdirectory(ice-client) - set_target_properties(ice-client PROPERTIES FOLDER "Tools") - - add_subdirectory(ktx-tool) - set_target_properties(ktx-tool PROPERTIES FOLDER "Tools") - - add_subdirectory(ac-client) - set_target_properties(ac-client PROPERTIES FOLDER "Tools") - - add_subdirectory(skeleton-dump) - set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") - - add_subdirectory(atp-client) - set_target_properties(atp-client PROPERTIES FOLDER "Tools") - - add_subdirectory(oven) - set_target_properties(oven PROPERTIES FOLDER "Tools") - - add_subdirectory(nitpick) - set_target_properties(nitpick PROPERTIES FOLDER "Tools") + foreach(TOOL ${ALL_TOOLS}) + check_test(${TOOL}) + if (${BUILD_TOOL_RESULT}) + add_subdirectory(${TOOL}) + set_target_properties(${TOOL} PROPERTIES FOLDER "Tools") + endif() + endforeach() endif() diff --git a/tools/frame-optimizer/CMakeLists.txt b/tools/frame-optimizer/CMakeLists.txt new file mode 100644 index 0000000000..cc268c5baf --- /dev/null +++ b/tools/frame-optimizer/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME frame-optimizer) + +setup_memory_debugger() +setup_hifi_project(Gui Widgets) +link_hifi_libraries(shared ktx shaders gpu ) +package_libraries_for_deployment() diff --git a/tools/frame-optimizer/src/main.cpp b/tools/frame-optimizer/src/main.cpp new file mode 100644 index 0000000000..a4200c3d97 --- /dev/null +++ b/tools/frame-optimizer/src/main.cpp @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis on 2018/10/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 + +#ifdef Q_OS_WIN +#include +#endif + +#include +#include +#include + + +gpu::IndexOptimizer optimizer= [](gpu::Primitive primitive, uint32_t faceCount, uint32_t indexCount, uint32_t* indices ) { + // FIXME add a triangle index optimizer here +}; + + +void messageHandler(QtMsgType type, const QMessageLogContext &, const QString & message) { + auto messageStr = message.toStdString(); +#ifdef Q_OS_WIN + OutputDebugStringA(messageStr.c_str()); + OutputDebugStringA("\n"); +#endif + std::cerr << messageStr << std::endl; +} + +int main(int argc, char** argv) { + QCoreApplication app(argc, argv); + qInstallMessageHandler(messageHandler); + gpu::optimizeFrame("D:/Frames/20190112_1647.json", optimizer); + return 0; +} diff --git a/tools/gpu-frame-player/CMakeLists.txt b/tools/gpu-frame-player/CMakeLists.txt new file mode 100644 index 0000000000..bd50839f9c --- /dev/null +++ b/tools/gpu-frame-player/CMakeLists.txt @@ -0,0 +1,19 @@ + +set(TARGET_NAME gpu-frame-player) + +setup_memory_debugger() +setup_hifi_project(Gui Widgets) + + +# link in the shared libraries +link_hifi_libraries( + shared ktx shaders gpu +# vk gpu-vk + gl ${PLATFORM_GL_BACKEND} +) + +target_compile_definitions(${TARGET_NAME} PRIVATE USE_GL) +target_opengl() +#target_vulkan() + +package_libraries_for_deployment() diff --git a/tools/gpu-frame-player/src/PlayerWindow.cpp b/tools/gpu-frame-player/src/PlayerWindow.cpp new file mode 100644 index 0000000000..7fbf43139f --- /dev/null +++ b/tools/gpu-frame-player/src/PlayerWindow.cpp @@ -0,0 +1,110 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "PlayerWindow.h" + +#include +#include +#include +#include + +#include + +PlayerWindow::PlayerWindow() { + installEventFilter(this); + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + +#ifdef USE_GL + setSurfaceType(QSurface::OpenGLSurface); +#else + setSurfaceType(QSurface::VulkanSurface); +#endif + + setGeometry(QRect(QPoint(), QSize(800, 600))); + create(); + show(); + // Ensure the window is visible and the GL context is valid + QCoreApplication::processEvents(); + _renderThread.initialize(this); +} + +PlayerWindow::~PlayerWindow() { +} + +bool PlayerWindow::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::Close) { + _renderThread.terminate(); + } + + return QWindow::eventFilter(obj, event); +} + +void PlayerWindow::loadFrame() { + static const QString LAST_FILE_KEY{ "lastFile" }; + auto lastScene = _settings.value(LAST_FILE_KEY); + QString openDir; + if (lastScene.isValid()) { + QFileInfo lastSceneInfo(lastScene.toString()); + if (lastSceneInfo.absoluteDir().exists()) { + openDir = lastSceneInfo.absolutePath(); + } + } + + QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), openDir, tr("GPU Frames (*.json)")); + if (fileName.isNull()) { + return; + } + _settings.setValue(LAST_FILE_KEY, fileName); + loadFrame(fileName); +} + +void PlayerWindow::keyPressEvent(QKeyEvent* event) { + switch (event->key()) { + case Qt::Key_F1: + loadFrame(); + return; + + default: + break; + } +} + +void PlayerWindow::resizeEvent(QResizeEvent* ev) { + _renderThread.resize(ev->size()); +} + +void PlayerWindow::textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) { + QImage image; + QImageReader(filename.c_str()).read(&image); + if (layer > 0) { + return; + } + texture->assignStoredMip(0, image.byteCount(), image.constBits()); +} + +void PlayerWindow::loadFrame(const QString& path) { + auto frame = gpu::readFrame(path.toStdString(), _renderThread._externalTexture, &PlayerWindow::textureLoader); + if (frame) { + _renderThread.submitFrame(frame); + if (!_renderThread.isThreaded()) { + _renderThread.process(); + } + } + if (frame->framebuffer) { + const auto& fbo = *frame->framebuffer; + glm::uvec2 size{ fbo.getWidth(), fbo.getHeight() }; + + auto screenSize = screen()->size(); + static const glm::uvec2 maxSize{ screenSize.width() - 100, screenSize.height() - 100 }; + while (glm::any(glm::greaterThan(size, maxSize))) { + size /= 2; + } + resize(size.x, size.y); + } + _renderThread.submitFrame(frame); +} diff --git a/tools/gpu-frame-player/src/PlayerWindow.h b/tools/gpu-frame-player/src/PlayerWindow.h new file mode 100644 index 0000000000..4dfbca0855 --- /dev/null +++ b/tools/gpu-frame-player/src/PlayerWindow.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once +#include +#include + +#include +#include "RenderThread.h" + +// Create a simple OpenGL window that renders text in various ways +class PlayerWindow : public QWindow { +public: + + PlayerWindow(); + virtual ~PlayerWindow(); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void resizeEvent(QResizeEvent* ev) override; + void loadFrame(); + void loadFrame(const QString& path); + +private: + static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer); + QSettings _settings; + RenderThread _renderThread; +}; diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp new file mode 100644 index 0000000000..608e8f250f --- /dev/null +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -0,0 +1,271 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "RenderThread.h" +#include +#include + +void RenderThread::submitFrame(const gpu::FramePointer& frame) { + std::unique_lock lock(_frameLock); + _pendingFrames.push(frame); +} + +void RenderThread::resize(const QSize& newSize) { + std::unique_lock lock(_frameLock); + _pendingSize.push(newSize); +} + +void RenderThread::initialize(QWindow* window) { + std::unique_lock lock(_frameLock); + setObjectName("RenderThread"); + Parent::initialize(); + + _window = window; +#ifdef USE_GL + _context.setWindow(window); + _context.create(); + _context.makeCurrent(); + QOpenGLContextWrapper(_context.qglContext()).makeCurrent(_window); + glGenTextures(1, &_externalTexture); + glBindTexture(GL_TEXTURE_2D, _externalTexture); + static const glm::u8vec4 color{ 0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color); + gl::setSwapInterval(0); + // GPU library init + gpu::Context::init(); + _context.makeCurrent(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _context.doneCurrent(); + _context.moveToThread(_thread); +#else + auto size = window->size(); + _extent = vk::Extent2D{ (uint32_t)size.width(), (uint32_t)size.height() }; + + _context.setValidationEnabled(true); + _context.requireExtensions({ + std::string{ VK_KHR_SURFACE_EXTENSION_NAME }, + std::string{ VK_KHR_WIN32_SURFACE_EXTENSION_NAME }, + }); + _context.requireDeviceExtensions({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }); + _context.createInstance(); + _surface = _context.instance.createWin32SurfaceKHR({ {}, GetModuleHandle(NULL), (HWND)window->winId() }); + _context.createDevice(_surface); + _swapchain.setSurface(_surface); + _swapchain.create(_extent, true); + + setupRenderPass(); + setupFramebuffers(); + + acquireComplete = _context.device.createSemaphore(vk::SemaphoreCreateInfo{}); + renderComplete = _context.device.createSemaphore(vk::SemaphoreCreateInfo{}); + + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); +#endif +} + +void RenderThread::setup() { + // Wait until the context has been moved to this thread + { std::unique_lock lock(_frameLock); } + _gpuContext->beginFrame(); + _gpuContext->endFrame(); + +#ifdef USE_GL + _context.makeCurrent(); + glViewport(0, 0, 800, 600); + (void)CHECK_GL_ERROR(); +#endif + _elapsed.start(); +} + +void RenderThread::shutdown() { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _gpuContext->shutdown(); + _gpuContext.reset(); +} + +#ifndef USE_GL +extern vk::CommandBuffer currentCommandBuffer; +#endif + +void RenderThread::renderFrame(gpu::FramePointer& frame) { + ++_presentCount; +#ifdef USE_GL + _context.makeCurrent(); +#endif + _backend->recycle(); + _backend->syncCache(); + + auto windowSize = _window->size(); + +#ifndef USE_GL + auto windowExtent = vk::Extent2D{ (uint32_t)windowSize.width(), (uint32_t)windowSize.height() }; + if (windowExtent != _extent) { + return; + } + + if (_extent != _swapchain.extent) { + _swapchain.create(_extent); + setupFramebuffers(); + return; + } + + static const vk::Offset2D offset; + static const std::array clearValues{ + vk::ClearColorValue(std::array{ { 0.2f, 0.2f, 0.2f, 0.2f } }), + vk::ClearDepthStencilValue({ 1.0f, 0 }), + }; + + auto swapchainIndex = _swapchain.acquireNextImage(acquireComplete).value; + auto framebuffer = _framebuffers[swapchainIndex]; + const auto& commandBuffer = currentCommandBuffer = _context.createCommandBuffer(); + + auto rect = vk::Rect2D{ offset, _extent }; + vk::RenderPassBeginInfo beginInfo{ _renderPass, framebuffer, rect, (uint32_t)clearValues.size(), clearValues.data() }; + commandBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); + + using namespace vks::debug::marker; + beginRegion(commandBuffer, "executeFrame", glm::vec4{ 1, 1, 1, 1 }); +#endif + + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + } + +#ifdef USE_GL + auto& glbackend = (gpu::gl::GLBackend&)(*_backend); + glm::uvec2 fboSize{ frame->framebuffer->getWidth(), frame->framebuffer->getHeight() }; + auto fbo = glbackend.getFramebufferID(frame->framebuffer); + glDisable(GL_FRAMEBUFFER_SRGB); + glBlitNamedFramebuffer(fbo, 0, 0, 0, fboSize.x, fboSize.y, 0, 0, windowSize.width(), windowSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + (void)CHECK_GL_ERROR(); + _context.swapBuffers(); + _context.doneCurrent(); +#else + endRegion(commandBuffer); + beginRegion(commandBuffer, "renderpass:testClear", glm::vec4{ 0, 1, 1, 1 }); + commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eInline); + commandBuffer.endRenderPass(); + endRegion(commandBuffer); + commandBuffer.end(); + + static const vk::PipelineStageFlags waitFlags{ vk::PipelineStageFlagBits::eBottomOfPipe }; + vk::SubmitInfo submitInfo{ 1, &acquireComplete, &waitFlags, 1, &commandBuffer, 1, &renderComplete }; + vk::Fence frameFence = _context.device.createFence(vk::FenceCreateInfo{}); + _context.queue.submit(submitInfo, frameFence); + _swapchain.queuePresent(renderComplete); + _context.trashCommandBuffers({ commandBuffer }); + _context.emptyDumpster(frameFence); + _context.recycle(); +#endif +} + +bool RenderThread::process() { + std::queue pendingFrames; + std::queue pendingSize; + + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + pendingSize.swap(_pendingSize); + } + + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + _gpuContext->consumeFrameUpdates(_activeFrame); + pendingFrames.pop(); + } + + while (!pendingSize.empty()) { +#ifndef USE_GL + const auto& size = pendingSize.front(); + _extent = { (uint32_t)size.width(), (uint32_t)size.height() }; +#endif + pendingSize.pop(); + } + + if (!_activeFrame) { + QThread::msleep(1); + return true; + } + + renderFrame(_activeFrame); + return true; +} + +#ifndef USE_GL + +void RenderThread::setupFramebuffers() { + // Recreate the frame buffers + _context.trashAll(_framebuffers, [this](const std::vector& framebuffers) { + for (const auto& framebuffer : framebuffers) { + _device.destroy(framebuffer); + } + }); + + vk::ImageView attachment; + vk::FramebufferCreateInfo framebufferCreateInfo; + framebufferCreateInfo.renderPass = _renderPass; + framebufferCreateInfo.attachmentCount = 1; + framebufferCreateInfo.pAttachments = &attachment; + framebufferCreateInfo.width = _extent.width; + framebufferCreateInfo.height = _extent.height; + framebufferCreateInfo.layers = 1; + + // Create frame buffers for every swap chain image + _framebuffers = _swapchain.createFramebuffers(framebufferCreateInfo); +} + +void RenderThread::setupRenderPass() { + if (_renderPass) { + _device.destroy(_renderPass); + } + + vk::AttachmentDescription attachment; + // Color attachment + attachment.format = _swapchain.colorFormat; + attachment.loadOp = vk::AttachmentLoadOp::eClear; + attachment.storeOp = vk::AttachmentStoreOp::eStore; + attachment.initialLayout = vk::ImageLayout::eUndefined; + attachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; + + vk::AttachmentReference colorAttachmentReference; + colorAttachmentReference.attachment = 0; + colorAttachmentReference.layout = vk::ImageLayout::eColorAttachmentOptimal; + + vk::SubpassDescription subpass; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentReference; + vk::SubpassDependency subpassDependency; + subpassDependency.srcSubpass = 0; + subpassDependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + subpassDependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + subpassDependency.dstSubpass = VK_SUBPASS_EXTERNAL; + subpassDependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead; + subpassDependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + + vk::RenderPassCreateInfo renderPassInfo; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &attachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &subpassDependency; + _renderPass = _device.createRenderPass(renderPassInfo); +} +#endif diff --git a/tools/gpu-frame-player/src/RenderThread.h b/tools/gpu-frame-player/src/RenderThread.h new file mode 100644 index 0000000000..7312fece6c --- /dev/null +++ b/tools/gpu-frame-player/src/RenderThread.h @@ -0,0 +1,68 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once + +#include + +#include +#include + +#ifdef USE_GL +#include +#include +#include +#else +#include +#include +#endif + +class RenderThread : public GenericThread { + using Parent = GenericThread; +public: + QWindow* _window{ nullptr }; + +#ifdef USE_GL + gl::Context _context; +#else + vks::Context& _context{ vks::Context::get() }; + const vk::Device& _device{ _context.device }; + + vk::SurfaceKHR _surface; + vk::RenderPass _renderPass; + vks::Swapchain _swapchain; + vk::Semaphore acquireComplete, renderComplete; + std::vector _framebuffers; + vk::Extent2D _extent; + + void setupFramebuffers(); + void setupRenderPass(); +#endif + + std::mutex _mutex; + gpu::ContextPointer _gpuContext; // initialized during window creation + std::shared_ptr _backend; + std::atomic _presentCount{ 0 }; + QElapsedTimer _elapsed; + size_t _frameIndex{ 0 }; + std::mutex _frameLock; + std::queue _pendingFrames; + std::queue _pendingSize; + gpu::FramePointer _activeFrame; + uint32_t _externalTexture{ 0 }; + + + void resize(const QSize& newSize); + void setup() override; + bool process() override; + void shutdown() override; + + void submitFrame(const gpu::FramePointer& frame); + void initialize(QWindow* window); + void renderFrame(gpu::FramePointer& frame); +}; diff --git a/tools/gpu-frame-player/src/main.cpp b/tools/gpu-frame-player/src/main.cpp new file mode 100644 index 0000000000..ac48ee2e1c --- /dev/null +++ b/tools/gpu-frame-player/src/main.cpp @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// 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 + +#include +#include "PlayerWindow.h" + +Q_DECLARE_LOGGING_CATEGORY(gpu_player_logging) +Q_LOGGING_CATEGORY(gpu_player_logging, "hifi.gpu.player") + +QSharedPointer logger; + +static const QString LAST_FRAME_FILE = "lastFrameFile"; + +static void setup() { + DependencyManager::set(); +} + +int main(int argc, char** argv) { + setupHifiApplication("gpuFramePlayer"); + + QApplication app(argc, argv); + logger.reset(new FileLogger()); + setup(); + PlayerWindow window; + app.exec(); + return 0; +} diff --git a/tools/nitpick/README.md b/tools/nitpick/README.md index 23105a0e02..3a664a12e9 100644 --- a/tools/nitpick/README.md +++ b/tools/nitpick/README.md @@ -13,36 +13,39 @@ Nitpick has 5 functions, separated into separate tabs: 1. Web interface ## Build (for developers) -Nitpick is built as part of the High Fidelity build. +Nitpick is built as part of the High Fidelity build. +XXXX refers to the version number - replace as necessary. For example, replace with 3.2.11 ### Creating installers #### Windows +1. Perform Release build 1. Verify that 7Zip is installed. 1. cd to the `build\tools\nitpick\Release` directory 1. Delete any existing installers (named nitpick-installer-###.exe) 1. Select all, right-click and select 7-Zip->Add to archive... 1. Set Archive format to 7z 1. Check "Create SFX archive -1. Enter installer name (i.e. `nitpick-installer-v1.2.exe`) +1. Enter installer name (i.e. `nitpick-installer-vXXXX.exe`) 1. Click "OK" -1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe: aws s3 cp nitpick-installer-v1.2.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.2.exe +1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe: aws s3 cp nitpick-installer-vXXXX.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.exe #### Mac These steps assume the hifi repository has been cloned to `~/hifi`. 1. (first time) Install brew In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)` 1. (First time) install create-dmg: In a terminal: `brew install create-dmg` +1. Perform Release build 1. In a terminal: cd to the `build/tools/nitpick/Release` folder 1. Copy the quazip dynamic library (note final period): In a terminal: `cp ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib .` 1. Change the loader instruction to find the dynamic library locally In a terminal: `install_name_tool -change ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib libquazip5.1.dylib nitpick` 1. Delete any existing disk images. In a terminal: `rm *.dmg` -1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-v1.2 nitpick-installer-v1.2.dmg .` +1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-vXXXX nitpick-installer-vXXXX.dmg .` Make sure to wait for completion. -1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-v1.2.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.2.dmg` +1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-vXXXX.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.dmg` ### Installation #### Windows -1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe) +1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe) 1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/) 1. After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable. 1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/ @@ -52,7 +55,7 @@ These steps assume the hifi repository has been cloned to `~/hifi`. 1. Leave region name and ouput format as default [None] 1. Install the latest release of Boto3 via pip: `pip install boto3` -1. Download the installer by browsing to [here]() +1. Download the installer by browsing to [here]() 1. Double click on the installer and install to a convenient location ![](./setup_7z.PNG) @@ -76,14 +79,14 @@ In a terminal: `python3 get-pip.py --user` 1. Enter the secret key 1. Leave region name and ouput format as default [None] 1. Install the latest release of Boto3 via pip: pip3 install boto3 -1. Download the installer by browsing to [here](). +1. Download the installer by browsing to [here](). 1. Double-click on the downloaded image to mount it 1. Create a folder for the nitpick files (e.g. ~/nitpick) If this folder exists then delete all it's contents. 1. Copy the downloaded files to the folder In a terminal: `cd ~/nitpick` - `cp -r /Volumes/nitpick-installer-v1.2/* .` + `cp -r /Volumes/nitpick-installer-vXXXX/* .` 1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__ # Usage diff --git a/tools/normalizeFrame.py b/tools/normalizeFrame.py new file mode 100644 index 0000000000..36b2038d5c --- /dev/null +++ b/tools/normalizeFrame.py @@ -0,0 +1,59 @@ +import os +import json +import shutil +import sys + +def scriptRelative(*paths): + scriptdir = os.path.dirname(os.path.realpath(sys.argv[0])) + result = os.path.join(scriptdir, *paths) + result = os.path.realpath(result) + result = os.path.normcase(result) + return result + + + +class FrameProcessor: + def __init__(self, filename): + self.filename = filename + dir, name = os.path.split(self.filename) + self.dir = dir + self.ktxDir = os.path.join(self.dir, 'ktx') + os.makedirs(self.ktxDir, exist_ok=True) + self.resDir = scriptRelative("../interface/resources") + + if (name.endswith(".json")): + self.name = name[0:-5] + else: + self.name = name + self.filename = self.filename + '.json' + + with open(self.filename, 'r') as f: + self.json = json.load(f) + + + def processKtx(self, texture): + if texture is None: return + if not 'ktxFile' in texture: return + sourceKtx = texture['ktxFile'] + if sourceKtx.startswith(':'): + sourceKtx = os.path.join(self.resDir, sourceKtx[3:]) + sourceKtxDir, sourceKtxName = os.path.split(sourceKtx) + destKtx = os.path.join(self.ktxDir, sourceKtxName) + if not os.path.isfile(destKtx): + shutil.copy(sourceKtx, destKtx) + newValue = 'ktx/' + sourceKtxName + texture['ktxFile'] = newValue + + + def process(self): + for texture in self.json['textures']: + self.processKtx(texture) + + with open(self.filename, 'w') as f: + json.dump(self.json, f, indent=2) + +fp = FrameProcessor("D:/Frames/20190110_1635.json") +fp.process() + + +#C:\Users\bdavi\git\hifi\interface\resources\meshes \ No newline at end of file diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index b6470a7551..8f4d5a7962 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -12,7 +12,43 @@ using System; using System.IO; using System.Collections.Generic; -class AvatarExporter : MonoBehaviour { +class AvatarExporter : MonoBehaviour { + // update version number for every PR that changes this file, also set updated version in README file + static readonly string AVATAR_EXPORTER_VERSION = "0.1"; + + static readonly float HIPS_GROUND_MIN_Y = 0.01f; + static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; + static readonly int MAXIMUM_USER_BONE_COUNT = 256; + static readonly string EMPTY_WARNING_TEXT = "None"; + + static readonly string[] RECOMMENDED_UNITY_VERSIONS = new string[] { + "2018.2.12f1", + "2018.2.11f1", + "2018.2.10f1", + "2018.2.9f1", + "2018.2.8f1", + "2018.2.7f1", + "2018.2.6f1", + "2018.2.5f1", + "2018.2.4f1", + "2018.2.3f1", + "2018.2.2f1", + "2018.2.1f1", + "2018.2.0f2", + "2018.1.9f2", + "2018.1.8f1", + "2018.1.7f1", + "2018.1.6f1", + "2018.1.5f1", + "2018.1.4f1", + "2018.1.3f1", + "2018.1.2f1", + "2018.1.1f1", + "2018.1.0f2", + "2017.4.18f1", + "2017.4.17f1", + }; + static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { {"Chest", "Spine1"}, {"Head", "Head"}, @@ -70,70 +106,163 @@ class AvatarExporter : MonoBehaviour { {"UpperChest", "Spine2"}, }; - static readonly Dictionary referenceAbsoluteRotations = new Dictionary { + // absolute reference rotations for each Humanoid bone using Artemis fbx in Unity 2018.2.12f1 + static readonly Dictionary REFERENCE_ROTATIONS = new Dictionary { + {"Chest", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, {"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, {"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)}, - {"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, - {"LeftHandIndex2", new Quaternion(-0.4934928f, 0.5062312f, -0.5064303f, -0.4936835f)}, - {"LeftHandIndex1", new Quaternion(-0.4986293f, 0.5017503f, -0.5013659f, -0.4982448f)}, - {"LeftHandPinky3", new Quaternion(-0.490056f, 0.5143053f, -0.5095307f, -0.4855038f)}, - {"LeftHandPinky2", new Quaternion(-0.5083722f, 0.4954255f, -0.4915887f, -0.5044324f)}, - {"LeftHandPinky1", new Quaternion(-0.5062528f, 0.497324f, -0.4937346f, -0.5025966f)}, - {"LeftHandMiddle3", new Quaternion(-0.4871885f, 0.5123404f, -0.5125002f, -0.4873383f)}, - {"LeftHandMiddle2", new Quaternion(-0.5171652f, 0.4827828f, -0.4822642f, -0.5166069f)}, - {"LeftHandMiddle1", new Quaternion(-0.4955998f, 0.5041052f, -0.5043675f, -0.4958555f)}, - {"LeftHandRing3", new Quaternion(-0.4936301f, 0.5097645f, -0.5061787f, -0.4901562f)}, - {"LeftHandRing2", new Quaternion(-0.5089865f, 0.4943658f, -0.4909532f, -0.5054707f)}, - {"LeftHandRing1", new Quaternion(-0.5020972f, 0.5005084f, -0.4979034f, -0.4994819f)}, - {"LeftHandThumb3", new Quaternion(-0.6617184f, 0.2884935f, -0.3604706f, -0.5907297f)}, - {"LeftHandThumb2", new Quaternion(-0.6935627f, 0.1995147f, -0.2805665f, -0.6328092f)}, - {"LeftHandThumb1", new Quaternion(-0.6663674f, 0.278572f, -0.3507071f, -0.5961183f)}, + {"Left Index Distal", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, + {"Left Index Intermediate", new Quaternion(-0.4934928f, 0.5062312f, -0.5064303f, -0.4936835f)}, + {"Left Index Proximal", new Quaternion(-0.4986293f, 0.5017503f, -0.5013659f, -0.4982448f)}, + {"Left Little Distal", new Quaternion(-0.490056f, 0.5143053f, -0.5095307f, -0.4855038f)}, + {"Left Little Intermediate", new Quaternion(-0.5083722f, 0.4954255f, -0.4915887f, -0.5044324f)}, + {"Left Little Proximal", new Quaternion(-0.5062528f, 0.497324f, -0.4937346f, -0.5025966f)}, + {"Left Middle Distal", new Quaternion(-0.4871885f, 0.5123404f, -0.5125002f, -0.4873383f)}, + {"Left Middle Intermediate", new Quaternion(-0.5171652f, 0.4827828f, -0.4822642f, -0.5166069f)}, + {"Left Middle Proximal", new Quaternion(-0.4955998f, 0.5041052f, -0.5043675f, -0.4958555f)}, + {"Left Ring Distal", new Quaternion(-0.4936301f, 0.5097645f, -0.5061787f, -0.4901562f)}, + {"Left Ring Intermediate", new Quaternion(-0.5089865f, 0.4943658f, -0.4909532f, -0.5054707f)}, + {"Left Ring Proximal", new Quaternion(-0.5020972f, 0.5005084f, -0.4979034f, -0.4994819f)}, + {"Left Thumb Distal", new Quaternion(-0.6617184f, 0.2884935f, -0.3604706f, -0.5907297f)}, + {"Left Thumb Intermediate", new Quaternion(-0.6935627f, 0.1995147f, -0.2805665f, -0.6328092f)}, + {"Left Thumb Proximal", new Quaternion(-0.6663674f, 0.278572f, -0.3507071f, -0.5961183f)}, {"LeftEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, {"LeftFoot", new Quaternion(0.009215056f, 0.3612514f, 0.9323555f, -0.01121602f)}, {"LeftHand", new Quaternion(-0.4797408f, 0.5195366f, -0.5279632f, -0.4703038f)}, - {"LeftForeArm", new Quaternion(-0.4594738f, 0.4594729f, -0.5374805f, -0.5374788f)}, - {"LeftLeg", new Quaternion(-0.0005380471f, -0.03154583f, 0.9994993f, 0.002378627f)}, + {"LeftLowerArm", new Quaternion(-0.4594738f, 0.4594729f, -0.5374805f, -0.5374788f)}, + {"LeftLowerLeg", new Quaternion(-0.0005380471f, -0.03154583f, 0.9994993f, 0.002378627f)}, {"LeftShoulder", new Quaternion(-0.3840606f, 0.525857f, -0.5957767f, -0.47013f)}, - {"LeftToeBase", new Quaternion(-0.0002536641f, 0.7113448f, 0.7027079f, -0.01379319f)}, - {"LeftArm", new Quaternion(-0.4591927f, 0.4591916f, -0.5377204f, -0.5377193f)}, - {"LeftUpLeg", new Quaternion(-0.0006682819f, 0.0006864658f, 0.9999968f, -0.002333928f)}, + {"LeftToes", new Quaternion(-0.0002536641f, 0.7113448f, 0.7027079f, -0.01379319f)}, + {"LeftUpperArm", new Quaternion(-0.4591927f, 0.4591916f, -0.5377204f, -0.5377193f)}, + {"LeftUpperLeg", new Quaternion(-0.0006682819f, 0.0006864658f, 0.9999968f, -0.002333928f)}, {"Neck", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, - {"RightHandIndex3", new Quaternion(0.5083892f, 0.4911618f, -0.4914584f, 0.5086939f)}, - {"RightHandIndex2", new Quaternion(0.4931984f, 0.5065879f, -0.5067145f, 0.4933202f)}, - {"RightHandIndex1", new Quaternion(0.4991491f, 0.5012957f, -0.5008481f, 0.4987026f)}, - {"RightHandPinky3", new Quaternion(0.4890696f, 0.5154139f, -0.5104482f, 0.4843578f)}, - {"RightHandPinky2", new Quaternion(0.5084175f, 0.495413f, -0.4915423f, 0.5044444f)}, - {"RightHandPinky1", new Quaternion(0.5069782f, 0.4965974f, -0.4930001f, 0.5033045f)}, - {"RightHandMiddle3", new Quaternion(0.4867662f, 0.5129694f, -0.5128888f, 0.4866894f)}, - {"RightHandMiddle2", new Quaternion(0.5167004f, 0.4833596f, -0.4827653f, 0.5160643f)}, - {"RightHandMiddle1", new Quaternion(0.4965845f, 0.5031784f, -0.5033959f, 0.4967981f)}, - {"RightHandRing3", new Quaternion(0.4933217f, 0.5102056f, -0.5064691f, 0.4897075f)}, - {"RightHandRing2", new Quaternion(0.5085972f, 0.494844f, -0.4913519f, 0.505007f)}, - {"RightHandRing1", new Quaternion(0.502959f, 0.4996676f, -0.4970418f, 0.5003144f)}, - {"RightHandThumb3", new Quaternion(0.6611374f, 0.2896575f, -0.3616535f, 0.5900872f)}, - {"RightHandThumb2", new Quaternion(0.6937408f, 0.1986776f, -0.279922f, 0.6331626f)}, - {"RightHandThumb1", new Quaternion(0.6664271f, 0.2783172f, -0.3505667f, 0.596253f)}, + {"Right Index Distal", new Quaternion(0.5083892f, 0.4911618f, -0.4914584f, 0.5086939f)}, + {"Right Index Intermediate", new Quaternion(0.4931984f, 0.5065879f, -0.5067145f, 0.4933202f)}, + {"Right Index Proximal", new Quaternion(0.4991491f, 0.5012957f, -0.5008481f, 0.4987026f)}, + {"Right Little Distal", new Quaternion(0.4890696f, 0.5154139f, -0.5104482f, 0.4843578f)}, + {"Right Little Intermediate", new Quaternion(0.5084175f, 0.495413f, -0.4915423f, 0.5044444f)}, + {"Right Little Proximal", new Quaternion(0.5069782f, 0.4965974f, -0.4930001f, 0.5033045f)}, + {"Right Middle Distal", new Quaternion(0.4867662f, 0.5129694f, -0.5128888f, 0.4866894f)}, + {"Right Middle Intermediate", new Quaternion(0.5167004f, 0.4833596f, -0.4827653f, 0.5160643f)}, + {"Right Middle Proximal", new Quaternion(0.4965845f, 0.5031784f, -0.5033959f, 0.4967981f)}, + {"Right Ring Distal", new Quaternion(0.4933217f, 0.5102056f, -0.5064691f, 0.4897075f)}, + {"Right Ring Intermediate", new Quaternion(0.5085972f, 0.494844f, -0.4913519f, 0.505007f)}, + {"Right Ring Proximal", new Quaternion(0.502959f, 0.4996676f, -0.4970418f, 0.5003144f)}, + {"Right Thumb Distal", new Quaternion(0.6611374f, 0.2896575f, -0.3616535f, 0.5900872f)}, + {"Right Thumb Intermediate", new Quaternion(0.6937408f, 0.1986776f, -0.279922f, 0.6331626f)}, + {"Right Thumb Proximal", new Quaternion(0.6664271f, 0.2783172f, -0.3505667f, 0.596253f)}, {"RightEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, {"RightFoot", new Quaternion(-0.009482829f, 0.3612484f, 0.9323512f, 0.01144584f)}, {"RightHand", new Quaternion(0.4797273f, 0.5195542f, -0.5279628f, 0.4702987f)}, - {"RightForeArm", new Quaternion(0.4594217f, 0.4594215f, -0.5375242f, 0.5375237f)}, - {"RightLeg", new Quaternion(0.0005446263f, -0.03177159f, 0.9994922f, -0.002395923f)}, + {"RightLowerArm", new Quaternion(0.4594217f, 0.4594215f, -0.5375242f, 0.5375237f)}, + {"RightLowerLeg", new Quaternion(0.0005446263f, -0.03177159f, 0.9994922f, -0.002395923f)}, {"RightShoulder", new Quaternion(0.3841222f, 0.5257177f, -0.5957286f, 0.4702966f)}, - {"RightToeBase", new Quaternion(0.0001034f, 0.7113398f, 0.7027067f, 0.01411251f)}, - {"RightArm", new Quaternion(0.4591419f, 0.4591423f, -0.537763f, 0.5377624f)}, - {"RightUpLeg", new Quaternion(0.0006750703f, 0.0008973633f, 0.9999966f, 0.002352045f)}, + {"RightToes", new Quaternion(0.0001034f, 0.7113398f, 0.7027067f, 0.01411251f)}, + {"RightUpperArm", new Quaternion(0.4591419f, 0.4591423f, -0.537763f, 0.5377624f)}, + {"RightUpperLeg", new Quaternion(0.0006750703f, 0.0008973633f, 0.9999966f, 0.002352045f)}, {"Spine", new Quaternion(-0.05427956f, 1.508558e-7f, -2.775203e-6f, 0.9985258f)}, - {"Spine1", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, - {"Spine2", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, + {"UpperChest", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, + }; + + // Humanoid mapping name suffixes for each set of appendages + static readonly string[] LEG_MAPPING_SUFFIXES = new string[] { + "UpperLeg", + "LowerLeg", + "Foot", + "Toes", + }; + static readonly string[] ARM_MAPPING_SUFFIXES = new string[] { + "Shoulder", + "UpperArm", + "LowerArm", + "Hand", + }; + static readonly string[] HAND_MAPPING_SUFFIXES = new string[] { + " Index Distal", + " Index Intermediate", + " Index Proximal", + " Little Distal", + " Little Intermediate", + " Little Proximal", + " Middle Distal", + " Middle Intermediate", + " Middle Proximal", + " Ring Distal", + " Ring Intermediate", + " Ring Proximal", + " Thumb Distal", + " Thumb Intermediate", + " Thumb Proximal", }; - static Dictionary userBoneToHumanoidMappings = new Dictionary(); - static Dictionary userParentNames = new Dictionary(); - static Dictionary userAbsoluteRotations = new Dictionary(); + enum BoneRule { + RecommendedUnityVersion, + SingleRoot, + NoDuplicateMapping, + NoAsymmetricalLegMapping, + NoAsymmetricalArmMapping, + NoAsymmetricalHandMapping, + HipsMapped, + SpineMapped, + SpineDescendantOfHips, + ChestMapped, + ChestDescendantOfSpine, + NeckMapped, + HeadMapped, + HeadDescendantOfChest, + EyesMapped, + HipsNotOnGround, + HipsSpineChestNotCoincident, + TotalBoneCountUnderLimit, + BoneRuleEnd, + }; + // rules that are treated as errors and prevent exporting, otherwise rules will show as warnings + static readonly BoneRule[] EXPORT_BLOCKING_BONE_RULES = new BoneRule[] { + BoneRule.HipsMapped, + BoneRule.SpineMapped, + BoneRule.ChestMapped, + BoneRule.HeadMapped, + }; + + class UserBoneInformation { + public string humanName; // bone name in Humanoid if it is mapped, otherwise "" + public string parentName; // parent user bone name + public int mappingCount; // number of times this bone is mapped in Humanoid + public Vector3 position; // absolute position + public Quaternion rotation; // absolute rotation + public BoneTreeNode boneTreeNode; + + public UserBoneInformation() { + humanName = ""; + parentName = ""; + mappingCount = 0; + position = new Vector3(); + rotation = new Quaternion(); + boneTreeNode = new BoneTreeNode(); + } + + public bool HasHumanMapping() { return !string.IsNullOrEmpty(humanName); } + } + + class BoneTreeNode { + public string boneName; + public List children = new List(); + + public BoneTreeNode() {} + public BoneTreeNode(string name) { + boneName = name; + } + } + + static Dictionary userBoneInfos = new Dictionary(); + static Dictionary humanoidToUserBoneMappings = new Dictionary(); + static BoneTreeNode userBoneTree = new BoneTreeNode(); + static Dictionary failedBoneRules = new Dictionary(); static string assetPath = ""; static string assetName = ""; static HumanDescription humanDescription; + [MenuItem("High Fidelity/Export New Avatar")] static void ExportNewAvatar() { @@ -144,6 +273,11 @@ class AvatarExporter : MonoBehaviour { static void UpdateAvatar() { ExportSelectedAvatar(true); } + + [MenuItem("High Fidelity/About")] + static void About() { + EditorUtility.DisplayDialog("About", "High Fidelity, Inc.\nAvatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION, "Ok"); + } static void ExportSelectedAvatar(bool updateAvatar) { string[] guids = Selection.assetGUIDs; @@ -163,14 +297,58 @@ class AvatarExporter : MonoBehaviour { return; } if (modelImporter.animationType != ModelImporterAnimationType.Human) { - EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in the Rig section of it's Inspector window.", "Ok"); + EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in " + + " the Rig section of it's Inspector window.", "Ok"); return; } - - humanDescription = modelImporter.humanDescription; - if (!SetJointMappingsAndParentNames()) { + + humanDescription = modelImporter.humanDescription; + SetUserBoneInformation(); + + // format resulting bone rule failure strings + // consider export-blocking bone rules to be errors and show them in an error dialog, + // and also include any other bone rule failures as warnings in the dialog + string boneErrors = ""; + string boneWarnings = ""; + foreach (var failedBoneRule in failedBoneRules) { + if (Array.IndexOf(EXPORT_BLOCKING_BONE_RULES, failedBoneRule.Key) >= 0) { + boneErrors += failedBoneRule.Value + "\n\n"; + } else { + boneWarnings += failedBoneRule.Value + "\n\n"; + } + } + if (!string.IsNullOrEmpty(boneErrors)) { + // if there are both errors and warnings then warnings will be displayed with errors in the error dialog + if (!string.IsNullOrEmpty(boneWarnings)) { + boneErrors = "Errors:\n\n" + boneErrors; + boneErrors += "Warnings:\n\n" + boneWarnings; + } + // remove ending newlines from the last rule failure string that was added above + boneErrors = boneErrors.Substring(0, boneErrors.LastIndexOf("\n\n")); + EditorUtility.DisplayDialog("Error", boneErrors, "Ok"); return; } + + if (!humanoidToUserBoneMappings.ContainsKey("UpperChest")) { + // if parent of Neck is not Chest then map the parent to UpperChest + string neckUserBone; + if (humanoidToUserBoneMappings.TryGetValue("Neck", out neckUserBone)) { + UserBoneInformation neckParentBoneInfo; + string neckParentUserBone = userBoneInfos[neckUserBone].parentName; + if (userBoneInfos.TryGetValue(neckParentUserBone, out neckParentBoneInfo) && !neckParentBoneInfo.HasHumanMapping()) { + neckParentBoneInfo.humanName = "UpperChest"; + humanoidToUserBoneMappings.Add("UpperChest", neckParentUserBone); + } + } + // if there is still no UpperChest bone but there is a Chest bone then we remap Chest to UpperChest + string chestUserBone; + if (!humanoidToUserBoneMappings.ContainsKey("UpperChest") && + humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) { + userBoneInfos[chestUserBone].humanName = "UpperChest"; + humanoidToUserBoneMappings.Remove("Chest"); + humanoidToUserBoneMappings.Add("UpperChest", chestUserBone); + } + } string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); string hifiFolder = documentsFolder + "\\High Fidelity Projects"; @@ -236,11 +414,12 @@ class AvatarExporter : MonoBehaviour { modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; modelImporter.animationType = ModelImporterAnimationType.Human; EditorUtility.SetDirty(modelImporter); - modelImporter.SaveAndReimport(); - humanDescription = modelImporter.humanDescription; + modelImporter.SaveAndReimport(); - // redo joint mappings and parent names due to the fbx change - SetJointMappingsAndParentNames(); + // redo parent names, joint mappings, and user bone positions due to the fbx change + // as well as re-check the bone rules for failures + humanDescription = modelImporter.humanDescription; + SetUserBoneInformation(); } } } else { @@ -277,19 +456,30 @@ class AvatarExporter : MonoBehaviour { // write out a new fst file in place of the old file WriteFST(exportFstPath, projectName); + + // display success dialog with any bone rule warnings + string successDialog = "Avatar successfully updated!"; + if (!string.IsNullOrEmpty(boneWarnings)) { + successDialog += "\n\nWarnings:\n" + boneWarnings; + } + EditorUtility.DisplayDialog("Success!", successDialog, "Ok"); } else { // Export New Avatar menu option // create High Fidelity Projects folder in user documents folder if it doesn't exist if (!Directory.Exists(hifiFolder)) { Directory.CreateDirectory(hifiFolder); } + if (string.IsNullOrEmpty(boneWarnings)) { + boneWarnings = EMPTY_WARNING_TEXT; + } + // open a popup window to enter new export project name and project location ExportProjectWindow window = ScriptableObject.CreateInstance(); - window.Init(hifiFolder, OnExportProjectWindowClose); + window.Init(hifiFolder, boneWarnings, OnExportProjectWindowClose); } } - static void OnExportProjectWindowClose(string projectDirectory, string projectName) { + static void OnExportProjectWindowClose(string projectDirectory, string projectName, string warnings) { // copy the fbx from the Unity Assets folder to the project directory string exportModelPath = projectDirectory + assetName + ".fbx"; File.Copy(assetPath, exportModelPath); @@ -304,94 +494,19 @@ class AvatarExporter : MonoBehaviour { string exportFstPath = projectDirectory + "avatar.fst"; WriteFST(exportFstPath, projectName); - // remove any double slashes in texture directory path and warn user to copy external textures over + // remove any double slashes in texture directory path, display success dialog with any + // bone warnings previously mentioned, and suggest user to copy external textures over texturesDirectory = texturesDirectory.Replace("\\\\", "\\"); - EditorUtility.DisplayDialog("Warning", "If you are using any external textures with your model, " + - "please copy those textures to " + texturesDirectory, "Ok"); + string successDialog = "Avatar successfully exported!\n\n"; + if (warnings != EMPTY_WARNING_TEXT) { + successDialog += "Warnings:\n" + warnings; + } + successDialog += "Note: If you are using any external textures with your model, " + + "please copy those textures to " + texturesDirectory; + EditorUtility.DisplayDialog("Success!", successDialog, "Ok"); } - static bool SetJointMappingsAndParentNames() { - userParentNames.Clear(); - userBoneToHumanoidMappings.Clear(); - - // instantiate a game object of the user avatar to save out bone parents then destroy it - UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); - GameObject assetGameObject = (GameObject)Instantiate(avatarResource); - SetParentNames(assetGameObject.transform, userParentNames); - DestroyImmediate(assetGameObject); - - // store joint mappings only for joints that exist in hifi and verify missing required joints - HumanBone[] boneMap = humanDescription.human; - string chestUserBone = ""; - string neckUserBone = ""; - foreach (HumanBone bone in boneMap) { - string humanName = bone.humanName; - string boneName = bone.boneName; - string hifiJointName; - if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) { - userBoneToHumanoidMappings.Add(boneName, humanName); - if (humanName == "Chest") { - chestUserBone = boneName; - } else if (humanName == "Neck") { - neckUserBone = boneName; - } - } - - } - if (!userBoneToHumanoidMappings.ContainsValue("Hips")) { - EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok"); - return false; - } - if (!userBoneToHumanoidMappings.ContainsValue("Spine")) { - EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok"); - return false; - } - if (!userBoneToHumanoidMappings.ContainsValue("Chest")) { - // check to see if there is a child of Spine that could be mapped to Chest - string spineChild = ""; - foreach (var parentRelation in userParentNames) { - string humanName; - if (userBoneToHumanoidMappings.TryGetValue(parentRelation.Value, out humanName) && humanName == "Spine") { - if (spineChild == "") { - spineChild = parentRelation.Key; - } else { - // found more than one Spine child so we can't choose one to remap - spineChild = ""; - break; - } - } - } - if (spineChild != "" && !userBoneToHumanoidMappings.ContainsKey(spineChild)) { - // use child of Spine as Chest - userBoneToHumanoidMappings.Add(spineChild, "Chest"); - chestUserBone = spineChild; - } else { - EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); - return false; - } - } - if (!userBoneToHumanoidMappings.ContainsValue("UpperChest")) { - //if parent of Neck is not Chest then map the parent to UpperChest - if (neckUserBone != "") { - string neckParentUserBone, neckParentHuman; - userParentNames.TryGetValue(neckUserBone, out neckParentUserBone); - userBoneToHumanoidMappings.TryGetValue(neckParentUserBone, out neckParentHuman); - if (neckParentHuman != "Chest" && !userBoneToHumanoidMappings.ContainsKey(neckParentUserBone)) { - userBoneToHumanoidMappings.Add(neckParentUserBone, "UpperChest"); - } - } - // if there is still no UpperChest bone but there is a Chest bone then we remap Chest to UpperChest - if (!userBoneToHumanoidMappings.ContainsValue("UpperChest") && chestUserBone != "") { - userBoneToHumanoidMappings[chestUserBone] = "UpperChest"; - } - } - - return true; - } - - static void WriteFST(string exportFstPath, string projectName) { - userAbsoluteRotations.Clear(); - + static void WriteFST(string exportFstPath, string projectName) { // write out core fields to top of fst file try { File.WriteAllText(exportFstPath, "name = " + projectName + "\ntype = body+head\nscale = 1\nfilename = " + @@ -403,49 +518,53 @@ class AvatarExporter : MonoBehaviour { } // write out joint mappings to fst file - foreach (var jointMapping in userBoneToHumanoidMappings) { - string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[jointMapping.Value]; - File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + jointMapping.Key + "\n"); + foreach (var userBoneInfo in userBoneInfos) { + if (userBoneInfo.Value.HasHumanMapping()) { + string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.Value.humanName]; + File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + userBoneInfo.Key + "\n"); + } } // calculate and write out joint rotation offsets to fst file SkeletonBone[] skeletonMap = humanDescription.skeleton; foreach (SkeletonBone userBone in skeletonMap) { string userBoneName = userBone.name; - Quaternion userBoneRotation = userBone.rotation; - - string parentName; - userParentNames.TryGetValue(userBoneName, out parentName); - if (parentName == "root") { - // if the parent is root then use bone's rotation - userAbsoluteRotations.Add(userBoneName, userBoneRotation); - } else { - // otherwise multiply bone's rotation by parent bone's absolute rotation - userAbsoluteRotations.Add(userBoneName, userAbsoluteRotations[parentName] * userBoneRotation); + UserBoneInformation userBoneInfo; + if (!userBoneInfos.TryGetValue(userBoneName, out userBoneInfo)) { + continue; } - // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones in user avatar + Quaternion userBoneRotation = userBone.rotation; + string parentName = userBoneInfo.parentName; + if (parentName == "root") { + // if the parent is root then use bone's rotation + userBoneInfo.rotation = userBoneRotation; + } else { + // otherwise multiply bone's rotation by parent bone's absolute rotation + userBoneInfo.rotation = userBoneInfos[parentName].rotation * userBoneRotation; + } + + // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones Quaternion jointOffset = new Quaternion(); - string humanName, outputJointName = ""; - if (userBoneToHumanoidMappings.TryGetValue(userBoneName, out humanName)) { - outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[humanName]; - Quaternion rotation = referenceAbsoluteRotations[outputJointName]; - jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * rotation; - } else if (userAbsoluteRotations.ContainsKey(userBoneName)) { + string outputJointName = ""; + if (userBoneInfo.HasHumanMapping()) { + outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.humanName]; + Quaternion rotation = REFERENCE_ROTATIONS[userBoneInfo.humanName]; + jointOffset = Quaternion.Inverse(userBoneInfo.rotation) * rotation; + } else { outputJointName = userBoneName; - string lastRequiredParent = FindLastRequiredParentBone(userBoneName); - if (lastRequiredParent == "root") { - jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]); - } else { + jointOffset = Quaternion.Inverse(userBoneInfo.rotation); + string lastRequiredParent = FindLastRequiredAncestorBone(userBoneName); + if (lastRequiredParent != "root") { // take the previous offset and multiply it by the current local when we have an extra joint - string lastRequiredParentHifiName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneToHumanoidMappings[lastRequiredParent]]; - Quaternion lastRequiredParentRotation = referenceAbsoluteRotations[lastRequiredParentHifiName]; - jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * lastRequiredParentRotation; + string lastRequiredParentHumanName = userBoneInfos[lastRequiredParent].humanName; + Quaternion lastRequiredParentRotation = REFERENCE_ROTATIONS[lastRequiredParentHumanName]; + jointOffset *= lastRequiredParentRotation; } } // swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst - if (outputJointName != "") { + if (!string.IsNullOrEmpty(outputJointName)) { jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); @@ -455,48 +574,326 @@ class AvatarExporter : MonoBehaviour { // open File Explorer to the project directory once finished System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); } - - static void SetParentNames(Transform modelBone, Dictionary parentNames) { - for (int i = 0; i < modelBone.childCount; i++) { - SetParentNames(modelBone.GetChild(i), parentNames); + + static void SetUserBoneInformation() { + userBoneInfos.Clear(); + humanoidToUserBoneMappings.Clear(); + userBoneTree = new BoneTreeNode(); + + // instantiate a game object of the user avatar to traverse the bone tree to gather + // bone parents and positions as well as build a bone tree, then destroy it + UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); + GameObject assetGameObject = (GameObject)Instantiate(avatarResource); + TraverseUserBoneTree(assetGameObject.transform); + DestroyImmediate(assetGameObject); + + // iterate over Humanoid bones and update user bone info to increase human mapping counts for each bone + // as well as set their Humanoid name and build a Humanoid to user bone mapping + HumanBone[] boneMap = humanDescription.human; + foreach (HumanBone bone in boneMap) { + string humanName = bone.humanName; + string userBoneName = bone.boneName; + string hifiJointName; + if (userBoneInfos.ContainsKey(userBoneName)) { + ++userBoneInfos[userBoneName].mappingCount; + if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) { + userBoneInfos[userBoneName].humanName = humanName; + humanoidToUserBoneMappings.Add(humanName, userBoneName); + } + } } - if (modelBone.parent != null) { - parentNames.Add(modelBone.name, modelBone.parent.name); - } else { - parentNames.Add(modelBone.name, "root"); + + // generate the list of bone rule failure strings for any bone rules that are not satisfied by this avatar + SetFailedBoneRules(); + } + + static void TraverseUserBoneTree(Transform modelBone) { + GameObject gameObject = modelBone.gameObject; + + // check if this transform is a node containing mesh, light, or camera instead of a bone + bool mesh = gameObject.GetComponent() != null || gameObject.GetComponent() != null; + bool light = gameObject.GetComponent() != null; + bool camera = gameObject.GetComponent() != null; + + // if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name + if (!mesh && !light && !camera) { + UserBoneInformation userBoneInfo = new UserBoneInformation(); + userBoneInfo.position = modelBone.position; // bone's absolute position + + string boneName = modelBone.name; + if (modelBone.parent == null) { + // if no parent then this is actual root bone node of the user avatar, so consider it's parent as "root" + userBoneTree = new BoneTreeNode(boneName); // initialize root of tree + userBoneInfo.parentName = "root"; + userBoneInfo.boneTreeNode = userBoneTree; + } else { + // otherwise add this bone node as a child to it's parent's children list + string parentName = modelBone.parent.name; + BoneTreeNode boneTreeNode = new BoneTreeNode(boneName); + userBoneInfos[parentName].boneTreeNode.children.Add(boneTreeNode); + userBoneInfo.parentName = parentName; + } + + userBoneInfos.Add(boneName, userBoneInfo); + } + + // recurse over transform node's children + for (int i = 0; i < modelBone.childCount; ++i) { + TraverseUserBoneTree(modelBone.GetChild(i)); } } - static string FindLastRequiredParentBone(string currentBone) { + static string FindLastRequiredAncestorBone(string currentBone) { string result = currentBone; - while (result != "root" && !userBoneToHumanoidMappings.ContainsKey(result)) { - result = userParentNames[result]; + // iterating upward through user bone info parent names, find the first ancestor bone that is mapped in Humanoid + while (result != "root" && userBoneInfos.ContainsKey(result) && !userBoneInfos[result].HasHumanMapping()) { + result = userBoneInfos[result].parentName; } return result; } + + static void SetFailedBoneRules() { + failedBoneRules.Clear(); + + string hipsUserBone = ""; + string spineUserBone = ""; + string chestUserBone = ""; + string headUserBone = ""; + + Vector3 hipsPosition = new Vector3(); + + // iterate over all bone rules in order and add any rules that fail + // to the failed bone rules map with appropriate error or warning text + for (BoneRule boneRule = 0; boneRule < BoneRule.BoneRuleEnd; ++boneRule) { + switch (boneRule) { + case BoneRule.RecommendedUnityVersion: + if (Array.IndexOf(RECOMMENDED_UNITY_VERSIONS, Application.unityVersion) == -1) { + failedBoneRules.Add(boneRule, "The current version of Unity is not one of the recommended Unity " + + "versions. If you are using a version of Unity later than 2018.2.12f1, " + + "it is recommended to apply Enforce T-Pose under the Pose dropdown " + + "in Humanoid configuration."); + } + break; + case BoneRule.SingleRoot: + // bone rule fails if the root bone node has more than one child bone + if (userBoneTree.children.Count > 1) { + failedBoneRules.Add(boneRule, "There is more than one bone at the top level of the selected avatar's " + + "bone hierarchy. Please ensure all bones for Humanoid mappings are " + + "under the same bone hierarchy."); + } + break; + case BoneRule.NoDuplicateMapping: + // bone rule fails if any user bone is mapped to more than one Humanoid bone + foreach (var userBoneInfo in userBoneInfos) { + string boneName = userBoneInfo.Key; + int mappingCount = userBoneInfo.Value.mappingCount; + if (mappingCount > 1) { + string text = "The " + boneName + " bone is mapped to more than one bone in Humanoid."; + if (failedBoneRules.ContainsKey(boneRule)) { + failedBoneRules[boneRule] += "\n" + text; + } else { + failedBoneRules.Add(boneRule, text); + } + } + } + break; + case BoneRule.NoAsymmetricalLegMapping: + CheckAsymmetricalMappingRule(boneRule, LEG_MAPPING_SUFFIXES, "leg"); + break; + case BoneRule.NoAsymmetricalArmMapping: + CheckAsymmetricalMappingRule(boneRule, ARM_MAPPING_SUFFIXES, "arm"); + break; + case BoneRule.NoAsymmetricalHandMapping: + CheckAsymmetricalMappingRule(boneRule, HAND_MAPPING_SUFFIXES, "hand"); + break; + case BoneRule.HipsMapped: + hipsUserBone = CheckHumanBoneMappingRule(boneRule, "Hips"); + break; + case BoneRule.SpineMapped: + spineUserBone = CheckHumanBoneMappingRule(boneRule, "Spine"); + break; + case BoneRule.SpineDescendantOfHips: + CheckUserBoneDescendantOfHumanRule(boneRule, spineUserBone, "Hips"); + break; + case BoneRule.ChestMapped: + if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) { + // check to see if there is a child of Spine that we can suggest to be mapped to Chest + string spineChild = ""; + if (!string.IsNullOrEmpty(spineUserBone)) { + BoneTreeNode spineTreeNode = userBoneInfos[spineUserBone].boneTreeNode; + if (spineTreeNode.children.Count == 1) { + spineChild = spineTreeNode.children[0].boneName; + } + } + failedBoneRules.Add(boneRule, "There is no Chest bone mapped in Humanoid for the selected avatar."); + // if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping + if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) { + failedBoneRules[boneRule] += " It is suggested that you map bone " + spineChild + + " to Chest in Humanoid."; + } + } + break; + case BoneRule.ChestDescendantOfSpine: + CheckUserBoneDescendantOfHumanRule(boneRule, chestUserBone, "Spine"); + break; + case BoneRule.NeckMapped: + CheckHumanBoneMappingRule(boneRule, "Neck"); + break; + case BoneRule.HeadMapped: + headUserBone = CheckHumanBoneMappingRule(boneRule, "Head"); + break; + case BoneRule.HeadDescendantOfChest: + CheckUserBoneDescendantOfHumanRule(boneRule, headUserBone, "Chest"); + break; + case BoneRule.EyesMapped: + bool leftEyeMapped = humanoidToUserBoneMappings.ContainsKey("LeftEye"); + bool rightEyeMapped = humanoidToUserBoneMappings.ContainsKey("RightEye"); + if (!leftEyeMapped || !rightEyeMapped) { + if (leftEyeMapped && !rightEyeMapped) { + failedBoneRules.Add(boneRule, "There is no RightEye bone mapped in Humanoid " + + "for the selected avatar."); + } else if (!leftEyeMapped && rightEyeMapped) { + failedBoneRules.Add(boneRule, "There is no LeftEye bone mapped in Humanoid " + + "for the selected avatar."); + } else { + failedBoneRules.Add(boneRule, "There is no LeftEye or RightEye bone mapped in Humanoid " + + "for the selected avatar."); + } + } + break; + case BoneRule.HipsNotOnGround: + // ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y + if (!string.IsNullOrEmpty(hipsUserBone)) { + UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone]; + hipsPosition = hipsBoneInfo.position; + if (hipsPosition.y < HIPS_GROUND_MIN_Y) { + failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + + ") should not be at ground level."); + } + } + break; + case BoneRule.HipsSpineChestNotCoincident: + // ensure the bones mapped to Hips, Spine, and Chest are all not in the same position, + // check Hips to Spine and Spine to Chest lengths are within HIPS_SPINE_CHEST_MIN_SEPARATION + if (!string.IsNullOrEmpty(spineUserBone) && !string.IsNullOrEmpty(chestUserBone) && + !string.IsNullOrEmpty(hipsUserBone)) { + UserBoneInformation spineBoneInfo = userBoneInfos[spineUserBone]; + UserBoneInformation chestBoneInfo = userBoneInfos[chestUserBone]; + Vector3 hipsToSpine = hipsPosition - spineBoneInfo.position; + Vector3 spineToChest = spineBoneInfo.position - chestBoneInfo.position; + if (hipsToSpine.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION && + spineToChest.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION) { + failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + + "), the bone mapped to Spine in Humanoid (" + spineUserBone + + "), and the bone mapped to Chest in Humanoid (" + chestUserBone + + ") should not be coincidental."); + } + } + break; + case BoneRule.TotalBoneCountUnderLimit: + int userBoneCount = userBoneInfos.Count; + if (userBoneCount > MAXIMUM_USER_BONE_COUNT) { + failedBoneRules.Add(boneRule, "The total number of bones in the avatar (" + userBoneCount + + ") exceeds the maximum bone limit (" + MAXIMUM_USER_BONE_COUNT + ")."); + } + break; + } + } + } + + static string CheckHumanBoneMappingRule(BoneRule boneRule, string humanBoneName) { + string userBoneName = ""; + // bone rule fails if bone is not mapped in Humanoid + if (!humanoidToUserBoneMappings.TryGetValue(humanBoneName, out userBoneName)) { + failedBoneRules.Add(boneRule, "There is no " + humanBoneName + " bone mapped in Humanoid for the selected avatar."); + } + return userBoneName; + } + + static void CheckUserBoneDescendantOfHumanRule(BoneRule boneRule, string userBoneName, string descendantOfHumanName) { + if (string.IsNullOrEmpty(userBoneName)) { + return; + } + + string descendantOfUserBoneName = ""; + if (!humanoidToUserBoneMappings.TryGetValue(descendantOfHumanName, out descendantOfUserBoneName)) { + return; + } + + string userBone = userBoneName; + string ancestorUserBone = ""; + UserBoneInformation userBoneInfo = new UserBoneInformation(); + // iterate upward from user bone through user bone info parent names until root + // is reached or the ancestor bone name matches the target descendant of name + while (ancestorUserBone != "root") { + if (userBoneInfos.TryGetValue(userBone, out userBoneInfo)) { + ancestorUserBone = userBoneInfo.parentName; + if (ancestorUserBone == descendantOfUserBoneName) { + return; + } + userBone = ancestorUserBone; + } else { + break; + } + } + + // bone rule fails if no ancestor of given user bone matched the descendant of name (no early return) + failedBoneRules.Add(boneRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName + + ") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" + + descendantOfUserBoneName + ")."); + } + + static void CheckAsymmetricalMappingRule(BoneRule boneRule, string[] mappingSuffixes, string appendage) { + int leftCount = 0; + int rightCount = 0; + // add Left/Right to each mapping suffix to make Humanoid mapping names, + // and count the number of bones mapped in Humanoid on each side + foreach (string mappingSuffix in mappingSuffixes) { + string leftMapping = "Left" + mappingSuffix; + string rightMapping = "Right" + mappingSuffix; + if (humanoidToUserBoneMappings.ContainsKey(leftMapping)) { + ++leftCount; + } + if (humanoidToUserBoneMappings.ContainsKey(rightMapping)) { + ++rightCount; + } + } + // bone rule fails if number of left appendage mappings doesn't match number of right appendage mappings + if (leftCount != rightCount) { + failedBoneRules.Add(boneRule, "The number of bones mapped in Humanoid for the left " + appendage + " (" + + leftCount + ") does not match the number of bones mapped in Humanoid for the right " + + appendage + " (" + rightCount + ")."); + } + } } class ExportProjectWindow : EditorWindow { - const int MIN_WIDTH = 450; - const int MIN_HEIGHT = 250; + const int WINDOW_WIDTH = 500; + const int WINDOW_HEIGHT = 460; const int BUTTON_FONT_SIZE = 16; const int LABEL_FONT_SIZE = 16; const int TEXT_FIELD_FONT_SIZE = 14; const int TEXT_FIELD_HEIGHT = 20; const int ERROR_FONT_SIZE = 12; + const int WARNING_SCROLL_HEIGHT = 170; + const string EMPTY_ERROR_TEXT = "None\n"; string projectName = ""; string projectLocation = ""; string projectDirectory = ""; - string errorLabel = "\n"; + string errorText = EMPTY_ERROR_TEXT; + string warningText = ""; + Vector2 warningScrollPosition = new Vector2(0, 0); - public delegate void OnCloseDelegate(string projectDirectory, string projectName); + public delegate void OnCloseDelegate(string projectDirectory, string projectName, string warnings); OnCloseDelegate onCloseCallback; - public void Init(string initialPath, OnCloseDelegate closeCallback) { - minSize = new Vector2(MIN_WIDTH, MIN_HEIGHT); + public void Init(string initialPath, string warnings, OnCloseDelegate closeCallback) { + minSize = new Vector2(WINDOW_WIDTH, WINDOW_HEIGHT); + maxSize = new Vector2(WINDOW_WIDTH, WINDOW_HEIGHT); titleContent.text = "Export New Avatar"; projectLocation = initialPath; + warningText = warnings; onCloseCallback = closeCallback; ShowUtility(); } @@ -513,6 +910,9 @@ class ExportProjectWindow : EditorWindow { GUIStyle errorStyle = new GUIStyle(GUI.skin.label); errorStyle.fontSize = ERROR_FONT_SIZE; errorStyle.normal.textColor = Color.red; + errorStyle.wordWrap = true; + GUIStyle warningStyle = new GUIStyle(errorStyle); + warningStyle.normal.textColor = Color.yellow; GUILayout.Space(10); @@ -534,10 +934,20 @@ class ExportProjectWindow : EditorWindow { } } - // Red error label text to display any issues under text fields and Browse button - GUILayout.Label(errorLabel, errorStyle); + // Red error label text to display any file-related errors + GUILayout.Label("Error:", errorStyle); + GUILayout.Label(errorText, errorStyle); - GUILayout.Space(20); + GUILayout.Space(10); + + // Yellow warning label text to display scrollable list of any bone-related warnings + GUILayout.Label("Warnings:", warningStyle); + warningScrollPosition = GUILayout.BeginScrollView(warningScrollPosition, GUILayout.Width(WINDOW_WIDTH), + GUILayout.Height(WARNING_SCROLL_HEIGHT)); + GUILayout.Label(warningText, warningStyle); + GUILayout.EndScrollView(); + + GUILayout.Space(10); // Export button which will verify project folder can actually be created // before closing popup window and calling back to initiate the export @@ -546,7 +956,7 @@ class ExportProjectWindow : EditorWindow { export = true; if (!CheckForErrors(true)) { Close(); - onCloseCallback(projectDirectory, projectName); + onCloseCallback(projectDirectory, projectName, warningText); } } @@ -562,12 +972,12 @@ class ExportProjectWindow : EditorWindow { } bool CheckForErrors(bool exporting) { - errorLabel = "\n"; // default to no error + errorText = EMPTY_ERROR_TEXT; // default to None if no errors found projectDirectory = projectLocation + "\\" + projectName + "\\"; if (projectName.Length > 0) { // new project must have a unique folder name since the folder will be created for it if (Directory.Exists(projectDirectory)) { - errorLabel = "A folder with the name " + projectName + + errorText = "A folder with the name " + projectName + " already exists at that location.\nPlease choose a different project name or location."; return true; } @@ -575,7 +985,7 @@ class ExportProjectWindow : EditorWindow { if (projectLocation.Length > 0) { // before clicking Export we can verify that the project location at least starts with a drive if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') { - errorLabel = "Project location is invalid. Please choose a different project location.\n"; + errorText = "Project location is invalid. Please choose a different project location.\n"; return true; } } @@ -583,16 +993,16 @@ class ExportProjectWindow : EditorWindow { // when exporting, project name and location must both be defined, and project location must // be valid and accessible (we attempt to create the project folder at this time to verify this) if (projectName.Length == 0) { - errorLabel = "Please define a project name.\n"; + errorText = "Please define a project name.\n"; return true; } else if (projectLocation.Length == 0) { - errorLabel = "Please define a project location.\n"; + errorText = "Please define a project location.\n"; return true; } else { try { Directory.CreateDirectory(projectDirectory); } catch { - errorLabel = "Project location is invalid. Please choose a different project location.\n"; + errorText = "Project location is invalid. Please choose a different project location.\n"; return true; } } diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index 3ca4dbb1ee..f02bc688ae 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,3 +1,7 @@ +High Fidelity, Inc. +Avatar Exporter +Version 0.1 + Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. To create a new avatar project: diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 28052efea5..5e825bd0d9 100644 Binary files a/tools/unity-avatar-exporter/avatarExporter.unitypackage and b/tools/unity-avatar-exporter/avatarExporter.unitypackage differ